Problemas al Insertar con ADO.NET

25/09/2004 - 19:14 por Vannessa | Informe spam
Hola, tengo problemas al insertar en una BD SQL Server
2000, desde ADO.NET, lo hago desde un .vb, y los SELECT
funcionan bien, pero cuando quiero hacer un INSERT ya sea
con StoreProcedures o con sentencias ad-hoc,
sencillamente no lo hace. Aqui esta el código del
StoreProcedure:
**********************************************************
CREATE PROCEDURE SP_CustomerAdd
(
@customerIdInput int,
@customerName nvarchar(20),
@customerEmail nvarchar(20),
@customerPassword nvarchar(20),
)
AS
INSERT INTO customers
(
customerId,
customerName,
customerEmail,
customerPassword
)
VALUES
(
@customerIdInput,
@customerName,
@customerEmail,
@customerPassword
)
GO
**********************************************************

Y aqui, el codigo del archivo .vb, es un metodo que lo
llamo desde la página .aspx, en el boton_Click.

**********************************************************
Public Function AddCustomer(customerName As String,
customerEmail As String, customerPassword As String) As
String

'Crear instancia de los objetos Connection y Command

Dim myConnection As SqlConnection = New SqlConnection
(ConfigurationSettings.AppSettings("connex"))
Dim myCommand As New SqlCommand("SP_CustomerAdd",
myConnection)
myCommand.CommandType = CommandType.StoredProcedure

'Cuenta la cantidad de registros en la tabla "customers"
'Algoritmo para asignarle un consecutivo de ID al cliente
Dim customerAutoId As Integer
Dim contCustomers As Integer
Dim dsCont As New DataSet()
Dim dataRowCont As DataRow
Dim dsMax As New DataSet()
Dim dataRowMax As DataRow

Dim daCont As New SqlDataAdapter("SELECT COUNT
(customerId) AS COUNT FROM customers", myConnection)
daCont.Fill(dsCont, "COUNT")

For Each dataRowCont in dsCont.Tables("COUNT").Rows
contCustomers = CInt(dataRowCont("COUNT").ToString() - 1)
Next

If contCustomers > 0 Then
Dim daMax As New SqlDataAdapter("SELECT MAX(customerId)
AS MAX FROM customers", myConnection)
daMax.Fill(dsMax, "MAX")

For Each dataRowMax in dsMax.Tables("MAX").Rows
customerAutoId = CInt(dataRowMax("MAX").ToString() + 1)
Next
Else
customerAutoId = 1
End If

Return CStr(customerAutoId)

'Agregar parámetros al SPROC

Dim parameterCustomerIdInto As New SqlParameter
("@customerIdInput", SqlDbType.Int)
parameterCustomerIdInto.Direction =
ParameterDirection.Input
parameterCustomerIdInto.Value = customerAutoId
myCommand.Parameters.Add (parameterCustomerIdInto)

Dim parameterCustomerName As New SqlParameter
("@customerName", SqlDbType.NVarChar)
parameterCustomerName.Direction = ParameterDirection.Input
parameterCustomerName.Value = customerName
myCommand.Parameters.Add (parameterCustomerName)

Dim parameterCustomerEmail As New SqlParameter
("@customerEmail", SqlDbType.NVarChar)
parameterCustomerEmail.Direction =
ParameterDirection.Input
parameterCustomerEmail.Value = customerEmail
myCommand.Parameters.Add (parameterCustomerEmail)

Dim parameterCustomerPassword As New SqlParameter
("@customerPassword", SqlDbType.NVarChar)
parameterCustomerPassword.Direction =
ParameterDirection.Input
parameterCustomerPassword.Value = customerPassword
myCommand.Parameters.Add (parameterCustomerPassword)

Try
myConnection.Open()
myCommand.ExecuteNonQuery()
myConnection.Close()

Catch
Return String.Empty
End Try

End Function
**********************************************************
Si alguien pudiera ayudarme, estaria muy agradecida.
 

Leer las respuestas

#1 SqlRanger
27/09/2004 - 00:01 | Informe spam
Vanessa,

El método AddCustomer no inserta ningún registro sencillamente porque el
código que invoca al procedimiento almacenado nunca se ejecuta ya que hay
una instrucción Return justo antes, concretamente "Return
CStr(customerAutoId)".

De todas maneras, si me lo permites, me gustaría hacerte unas cuantas
sugerencias acerca del código que has enviado.

Sguramente te darás cuenta de que no necesitas recorrer todas las filas del
datatable, ya que sabes a priori que va a haber una única fila. En vez de:

For Each dataRowCont in dsCont.Tables("COUNT").Rows
contCustomers = CInt(dataRowCont("COUNT").ToString() - 1)
Next

podrías usar:

contCustomers = CInt(dsCont.Tables("COUNT").Rows(0).Item("COUNT")) -1

Por otra parte las instrucciones:

SELECT COUNT(customerId) AS COUNT FROM customers
SELECT MAX(customerId) AS MAX FROM customers

Devuelven cada una de ellas un único registro con un único campo. En este
caso lo más recomendable para obtener el valor es usar el método
ExecuteScalar de SqlCommand, en vez de crear un DataAdapter, un DataSet, y
usar el método Fill para llenarlo. ExecuteScalar es mucho más directo, más
eficiente y requiere menos código. O sea, en vez de:

Dim daCont As New SqlDataAdapter("SELECT COUNT (customerId) AS COUNT FROM
customers", myConnection)
daCont.Fill(dsCont, "COUNT")
For Each dataRowCont in dsCont.Tables("COUNT").Rows
contCustomers = CInt(dataRowCont("COUNT").ToString() - 1)
Next

Sería mejor usar:

Dim cmdCount As New SqlCommand("SELECT COUNT(customerId) FROM Customers",
myConnection)
myConnection.Open()
contCustomers = CInt( cmdCount.ExecuteScalar() ) ' creo que en realidad
el -1 sobra

Y en vez de:

If contCustomers > 0 Then
Dim daMax As New SqlDataAdapter("SELECT MAX(customerId) AS MAX FROM
customers", myConnection)
daMax.Fill(dsMax, "MAX")

For Each dataRowMax in dsMax.Tables("MAX").Rows
customerAutoId = CInt(dataRowMax("MAX").ToString() + 1)
Next
Else
customerAutoId = 1
End If

Sería mejor usar:

If contCustomers > 0 Then
Dim cmdMax As New SqlComand("SELECT MAX(customerId) FROM customers",
MyConnection)
customerAutoId = CInt( cmdMax.ExecuteScalar() ) + 1
Else
customerAutoId = 1
End If

Además, sabrás que no es necesario contar primero el número de registros. Si
la tabla no tiene registros "SELECT MAX(customerId) FROM customers"
devuelve NULL (DBNull en VB.NET). Así que te podrías ahorrar ejecutar una
instrucción SQL que, por cierto, puede llevar bastante tiempo en ejecutarse
si la tabla tiene muchas filas. Para que SQL Server te devuelva el número de
registros de una tabla, tiene que leerse toda la tabla completa. Sin embargo
para devolver el máximo, no, si existe un índice en el campo, y
presumiblemente lo tiene puesto que customerId supongo que es la clave
primaria, SQL Server busca el máximo en el índice. Para ello sólo tiene que
leer unas poquitas páginas de la base de datos.

Así que el código para obtener customerAutoId podría ser el siguiente:

Dim cmdMax As New SqlComand("SELECT MAX(customerId) FROM customers",
MyConnection)
MyConnection.Open()
Dim objCustomerAutoId As Object = cmdMax.ExecuteScalar()
If IsDBNull( objCustomerAutoId ) Then
customerAutoId = 1
Else
customerAutoId = CInt( objCustomerAutoId ) + 1
End If

Siguiendo con las sugerencias, te recomendaría que, ya que estás usando un
procedimiento almacenado para insertar el registro, pusieras la lógica para
calcular el siguiente customerId en el propio procedimiento almacenado,
ahorrándote así, código en VB.NET y encapsulando toda la lógica de inserción
en un único lugar, lo que evidentemente tiene sus ventajas.

El procedimiento almacenado podría ser el siguiente:

CREATE PROCEDURE SP_CustomerAdd
(
@customerId int OUTPUT,
@customerName nvarchar(20),
@customerEmail nvarchar(20),
@customerPassword nvarchar(20),
)
AS

SET @customerId = NULL
SELECT @customerId = MAX(customerId) FROM Customers
IF @CustomerId Is NULL
SET @CustomerId = 1
ELSE
@CustomerId = @CustomerId + 1

INSERT INTO customers
(
customerId,
customerName,
customerEmail,
customerPassword
)
VALUES
(
@customerId,
@customerName,
@customerEmail,
@customerPassword
)
GO

y el código del método AddCustomer el siguiente

Public Function AddCustomer(ByVal customerName As String, ByVal
customerEmail As String, ByVal customerPassword As String) As Integer
Dim cn As SqlConnection
Try
cn = New
SqlConnection(ConfigurationSettings.AppSettings("connex"))
Dim cmd As New SqlCommand("SP_CustomerAdd", cn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.Add("@customerId", SqlDbType.Int).Direction ParameterDirection.Output
cmd.Parameters.Add("@customerName", SqlDbType.NVarChar,
20).Value = customerName
cmd.Parameters.Add("@customerEmail", SqlDbType.NVarChar,
20).Value = customerEmail
cmd.Parameters.Add("@customerPassword", SqlDbType.NVarChar,
20).Value = customerPassword
cn.Open()
cmd.ExecuteNonQuery()
Return CInt(cmd.Parameters("@customerId").Value)
Catch ex As Exception
Throw New Exception("No se ha podido añadir el nuevo cliente, ya
que ha ocurrido el error: " & ex.Message, ex)
Finally
If cn.State = ConnectionState.Open Then cn.Close()
End Try
End Function

Observa que el método devuelve el código del nuevo cliente y que lanza una
execpción en caso de haber ocurrido un error. En el código que tenías,
capturabas la excepción y devolvías String.Empty en caso de error, o sea,
que estabas indicando el error por medio de un valor devuelto. Discúlpame,
pero esa forma de indicar los errores está ya obsoleta, es la forma que se
utilizaba en los leguajes como C, en las llamadas al API Win32 y en
lenguajes que no disponen de manejo estructurado de excepciones. En los
lenguajes con manejo estructurado de excepciones, los métodos deben lanzar
una excepción en caso de encontrar una condición de error. La excepción
subirá por la pila de llamadas hasta que encuente un método que lo capture,
generalmente será en la interfaz de usuario donde capturemos la excepción y
mostremos un mensaje de error al usuario.


Finalmente quisiera advertirte de que, tal y como está diseñado el proceso
de inserción de nuevos clientes, y si hay más de un usuario que esté
utilizando la aplicación, se corre el riesgo de que cuando dos usuarios
intentan añadir un nuevo cliente casi al mismo tiempo, el sistema les asigne
el mismo customerId, con lo cual, si customerId es clave primaria de la
tabla, la inserción fallaría, y si no es clave primaria, habría dos clientes
con el mismo customerId. Esto es así, porque SQL Server es capaz de ejecutar
al mismo tiempo varias instrucciones SQL en distintas conexiones. Por lo que
si SQL Server está ejecutando: SELECT MAX(CustomerId) FROM Customers en dos
conexiones diferentes al mismo tiempo, devolvería el mismo resultado y se
asignaría el mismo customerId a dos clientes diferentes. La solución más
sencilla a este problema es utilizar un autonumérico para customerId, así
SQL Server garantiza la unicidad de customerId. Otra solución es utilizar
bloqueos de aplicación, que pueden codificarse en el propio procedimiento
almacenado.

El siguiente procedimiento almacenado utiliza bloqueos de aplicación de tipo
sesión para garantizar que sólo un usuario añade un cliente al mismo tiempo:


CREATE PROCEDURE SP_CustomerAdd
(
@customerId int OUTPUT,
@customerName nvarchar(20),
@customerEmail nvarchar(20),
@customerPassword nvarchar(20),
)
AS

exec sp_getapplock 'nuevocliente', 'Exclusive', 'Session'

SET @customerId = NULL
SELECT @customerId = MAX(customerId) FROM Customers
IF @CustomerId Is NULL
SET @CustomerId = 1
ELSE
@CustomerId = @CustomerId + 1

INSERT INTO customers
(
customerId,
customerName,
customerEmail,
customerPassword
)
VALUES
(
@customerId,
@customerName,
@customerEmail,
@customerPassword
)

exec sp_releaseapplock 'nuevocliente', 'Session'

GO

De esta forma, si siempre se añaden nuevos clientes por medio de este
procedimiento almacenado, está garantizado que no se asignarán customerId
iguales a distintos clientes. Esto es así porque cuando desde una conexión
se ejecuta el procedimiento almacenado, al ejecutar exec sp_getapplock
'nuevocliente', 'Exclusive', 'Session', la conexión adquiere un bloqueo
exclusivo sobre el recurso 'nuevocliente' (el nombre es lo de menos, puede
ser cualquiera), y mientras la conexión no lo libere mediante exec
sp_releaseapplock 'nuevocliente', 'Session', cualquier otra conexión que
ejecute sp_getapplock 'nuevocliente', 'Exclusive', 'Session' se queda
bloqueado esperando hasta que se libere el bloqueo.

Espero haberte sido de ayuda.

Saludos:

Jesús López
MVP



"Vannessa" escribió en el mensaje
news:385801c4a323$108ac810$
Hola, tengo problemas al insertar en una BD SQL Server
2000, desde ADO.NET, lo hago desde un .vb, y los SELECT
funcionan bien, pero cuando quiero hacer un INSERT ya sea
con StoreProcedures o con sentencias ad-hoc,
sencillamente no lo hace. Aqui esta el código del
StoreProcedure:
**********************************************************
CREATE PROCEDURE SP_CustomerAdd
(
@customerIdInput int,
@customerName nvarchar(20),
@customerEmail nvarchar(20),
@customerPassword nvarchar(20),
)
AS
INSERT INTO customers
(
customerId,
customerName,
customerEmail,
customerPassword
)
VALUES
(
@customerIdInput,
@customerName,
@customerEmail,
@customerPassword
)
GO
**********************************************************

Y aqui, el codigo del archivo .vb, es un metodo que lo
llamo desde la página .aspx, en el boton_Click.

**********************************************************
Public Function AddCustomer(customerName As String,
customerEmail As String, customerPassword As String) As
String

'Crear instancia de los objetos Connection y Command

Dim myConnection As SqlConnection = New SqlConnection
(ConfigurationSettings.AppSettings("connex"))
Dim myCommand As New SqlCommand("SP_CustomerAdd",
myConnection)
myCommand.CommandType = CommandType.StoredProcedure

'Cuenta la cantidad de registros en la tabla "customers"
'Algoritmo para asignarle un consecutivo de ID al cliente
Dim customerAutoId As Integer
Dim contCustomers As Integer
Dim dsCont As New DataSet()
Dim dataRowCont As DataRow
Dim dsMax As New DataSet()
Dim dataRowMax As DataRow

Dim daCont As New SqlDataAdapter("SELECT COUNT
(customerId) AS COUNT FROM customers", myConnection)
daCont.Fill(dsCont, "COUNT")

For Each dataRowCont in dsCont.Tables("COUNT").Rows
contCustomers = CInt(dataRowCont("COUNT").ToString() - 1)
Next

If contCustomers > 0 Then
Dim daMax As New SqlDataAdapter("SELECT MAX(customerId)
AS MAX FROM customers", myConnection)
daMax.Fill(dsMax, "MAX")

For Each dataRowMax in dsMax.Tables("MAX").Rows
customerAutoId = CInt(dataRowMax("MAX").ToString() + 1)
Next
Else
customerAutoId = 1
End If

Return CStr(customerAutoId)

'Agregar parámetros al SPROC

Dim parameterCustomerIdInto As New SqlParameter
("@customerIdInput", SqlDbType.Int)
parameterCustomerIdInto.Direction ParameterDirection.Input
parameterCustomerIdInto.Value = customerAutoId
myCommand.Parameters.Add (parameterCustomerIdInto)

Dim parameterCustomerName As New SqlParameter
("@customerName", SqlDbType.NVarChar)
parameterCustomerName.Direction = ParameterDirection.Input
parameterCustomerName.Value = customerName
myCommand.Parameters.Add (parameterCustomerName)

Dim parameterCustomerEmail As New SqlParameter
("@customerEmail", SqlDbType.NVarChar)
parameterCustomerEmail.Direction ParameterDirection.Input
parameterCustomerEmail.Value = customerEmail
myCommand.Parameters.Add (parameterCustomerEmail)

Dim parameterCustomerPassword As New SqlParameter
("@customerPassword", SqlDbType.NVarChar)
parameterCustomerPassword.Direction ParameterDirection.Input
parameterCustomerPassword.Value = customerPassword
myCommand.Parameters.Add (parameterCustomerPassword)

Try
myConnection.Open()
myCommand.ExecuteNonQuery()
myConnection.Close()

Catch
Return String.Empty
End Try

End Function
**********************************************************
Si alguien pudiera ayudarme, estaria muy agradecida.

Preguntas similares