2009-09-03 30 views
7

Mi inserto procedimiento almacenado:SQL Server y .NET: inserción falla (! Silencio) en el código, pero no cuando se ejecuta manualmente

ALTER procedure proj_ins_all 
(
@proj_number INT, 
@usr_id INT, 
@download DATETIME, 
@status INT 
) 
as 
INSERT INTO project 
(proj_number, usr_id, date_download, status_id) 
VALUES 
(@proj_number, @usr_id, @download, @status) 
select SCOPE_IDENTITY() 

... funciona muy bien cuando se le llama de forma manual, así:

exec proj_ins_all 9001210, 2, '2009-09-03', 2 

... pero cuando se llama desde código:

_id = data.ExecuteIntScalar("proj_ins_all", arrParams); 

... el inserto no sucede. Ahora, la columna de identidad se incrementa y _id establece se establece en su valor. Pero la fila en sí nunca aparece en la tabla.

La mejor conjetura que pude encontrar fue un disparador de inserción que elimina la fila recién insertada, pero no hay activadores en la tabla (¿y por qué funcionaría cuando se realiza manualmente?). Mis otros intentos estaban alrededor de una conjetura de que el procedimiento almacenado está volviendo a hacer la inserción de alguna manera, y poniendo comienzan y final y van y punto y coma en el proceso almacenado para separar correctamente el 'inserto' y el ' selección de identidad 'bits. Eso no solucionó nada.

¿Alguna idea?

Actualización:

Gracias a todos los que han ayudado hasta ahora. En la sugerencia de Preet (primera respuesta) aprendí a usar SQL Server Profiler (no puedo creer que nunca lo haya sabido antes - pensé que solo era útil para ajustar el rendimiento, no me di cuenta de que podía ver exactamente qué consulta iba a suceder) a la DB con eso).

Reveló que el SQL enviado por el método SqlCommand.ExecuteScalar() era ligeramente diferente de lo que estaba ejecutando manualmente. Estaba enviando:

exec proj_ins_all @proj_number=9001810,@usr_id=2,@download='2009-09-03 16:20:11.7130000',@status=2 

Funcioné eso manualmente y listo! Un error real del servidor SQL (!):

Error al convertir el tipo de datos varchar a datetime.

Desde que estaba probando manualmente, simplemente acortado la fecha y hora de '2009-09-03 16: 20: 11.7130000' a '2009-09-03 16:20:11' y esto fijos el error; ahora la fila inserta bien.

Pero esto plantea la pregunta: ¿por qué Microsoft SQL Server no puede manejar más de 23 caracteres en ese parámetro de fecha y hora? Fue el método SqlCommand.ExecuteScalar() de Microsoft que construyó la consulta así, no yo. Este es un problema porque mi código aún no funciona.

Tan pronto como lo puse a trabajar manualmente, miré cómo configurar el parámetro SqlParameter para la fecha en el código para que envíe un valor como el que funcionó. Traté de cambiar el tipo de datos de SqlDbType.DateTime a SqlDbType.SmallDateTime. El generador de perfiles mostró que esto de hecho producía un valor de fecha más corto '2009-09-03 17:15:00', pero el inserto aún fallaba silenciosamente (el problema original).Pero cuando copié-pegué el sql del perfilador y lo intenté manualmente, funcionó. No hay error. Mismo trato al enviarlo como varchar: a la ventana de consulta SSMS le gusta, la misma consulta a través de .net falla silenciosamente.

:(

¿Alguna otra idea? Algunos configuración del tipo 'medio ambiente' en el servidor SQL como 'conjunto ANSI_NULLS off' o sea lo que sea diferente entre las conexiones a través de manuales y de código?

(Otra pregunta es ¿por qué servidor no sql me da un mensaje de error y generar una excepción cada vez que se produce este error?) código

.Net

A petición de Van, h ERE es el código .NET relevante (C# ASP.NET):

data.MakeInParam("@proj_number", SqlDbType.Int, _projNo), 
data.MakeInParam("@usr_id", SqlDbType.Int, _usr.Id), 
data.MakeInParam("@download", SqlDbType.SmallDateTime, _downloadDate), 
data.MakeInParam("@status", SqlDbType.Int, (int)_status), 

y MakeInParam tiene esta línea:

param = new SqlParameter(paramName, DbType); 

Y el fichero es ejecutado como esto:

SqlCommand cmd = CreateCommand(procName, prams); 
object result = cmd.ExecuteScalar(); 

Nota: en los parámetros anteriores, es @download ese es el problema. _downloadDate es un DateTime que se puede nulos. tenga en cuenta que el tipo sql en esta línea solía ser SqlDbType.DateTime, pero lo cambié a SqlDbType.SmallDateTime para producir una llamada que funciona manualmente (Todavía falla cuando se ejecuta desde el código).

Actualización: resuelve Gracias a Matt, por fin he encontrado y solucionado el problema: la clase de ayuda personalizada le faltaba un cmd.Transaction.Commit() en una de Sus funciones! Tenía un montón de diferentes cosas desordenadas y me perdí esto cuando revisé el código inicialmente.

Así que gracias a Matt y Preet y a todos los demás que contribuyeron.

Lecciones aprendidas
- Analizador de SQL es realmente útil. Tómese el tiempo para aprender cómo usarlo.
- Si el DB está tragando errores, o parece estar haciendo algo de acuerdo con su código, pero en realidad no aparece en el DB, verifique que la transacción se está cometiendo.
- No confíes tanto en una clase de ayudante de DB que alguien más escribió. Incluso uno que parece lo suficientemente simple al principio. Asegúrese de comprender exactamente lo que hace, ya que puede tener errores o simplemente no hacer las cosas de la manera en que cree que siempre se hacen.

Respuesta

5

La columna de identidad se incrementará siempre que se intente una inserción, incluso si falla, por lo que esa parte no es necesariamente inusual.

Buscaría problemas con los valores de sus parámetros y/o tipos en arrParams. ¿Qué tipo de objeto es 'datos'? (Estoy casi miedo de preguntar, pero yo no recibo ningún accesos a MSDN para ExecuteIntScalar)

EDIT:

Creo que van está en el camino correcto con respecto a la transacción no está comprometido .Se pierde como si estuvieras utilizando algún tipo de clase de ayuda personalizada para administrar llamadas a procedimientos almacenados en la base de datos (y otros accesos a bases de datos, presumiblemente), y podría ser que este código se esté tragando el error provocado por el servidor SQL. Creé una pequeña aplicación de prueba y pude reproducir el comportamiento que describes. Como no podemos decir cómo su clase atrapa excepciones, etc., este puede no ser su problema real, pero es una forma de que una llamada a procedimiento almacenado podría fallar en la manera que usted describe.

// call the proj_ins_all SP every time a button is clicked. 
protected void Button1_Click(object sender, EventArgs e) 
{ 
    using (SqlConnection conn = new SqlConnection(myConnectionString)) 
    using (SqlCommand cmd = new SqlCommand("proj_ins_all", conn)) 
    { 
     try 
     { 
      cmd.CommandType = CommandType.StoredProcedure; 
      cmd.Parameters.Add(new SqlParameter("@proj_number", SqlDbType.Int)); 
      cmd.Parameters["@proj_number"].Value = 9001810; 
      cmd.Parameters.Add(new SqlParameter("@usr_id", SqlDbType.Int)); 
      cmd.Parameters["@usr_id"].Value = 2; 
      cmd.Parameters.Add(new SqlParameter("@download", SqlDbType.SmallDateTime)); 
      cmd.Parameters["@download"].Value = "2009-09-03 16:20:11"; 
      cmd.Parameters.Add(new SqlParameter("@status", SqlDbType.Int)); 
      cmd.Parameters["@status"].Value = 2; 

      conn.Open(); 
      cmd.Transaction = conn.BeginTransaction(); 

      object _id = cmd.ExecuteScalar(); 

      // _id now contains the value of the Identity column for 
      // the row just inserted by proj_ins_all 

      // Assume (or simulate) an error is raised after the SP is called but 
      // before the transaction is committed. 
      // (Comment this line out and the DB update succeeds, as expected.) 
      throw new Exception(); 

      // If the transaction is not committed, it'll be rolled back when 
      // the connection is closed and the inserted row won't be in the 
      // table even though the incremented Identity value was returned above. 
      cmd.Transaction.Commit(); 

     } 
     catch (Exception) 
     { 
      // "swallow" (i.e. just ignore) any errors raised by the DB code. 
      //throw; 
     } 
    } 

} 

Las transacciones no tienen que declararse explícitamente. Por ejemplo, si eliminaste las llamadas a BeginTransaction() y Transaction.Commit() del código anterior, aún habría una transacción implícita que teóricamente podría ser interrumpida y causada por una reversión. Por lo tanto, la raíz de su problema podría ser más sutil que este ejemplo, que requiere una transacción explícita para demostrar el concepto.

Más prácticamente, puede ver (a través de Profiler) el SQL real que la aplicación envía al servidor SQL y verificar que funciona cuando se ejecuta desde SSMS, por lo que creo que el problema es probablemente con el código de la aplicación que llama el procedimiento almacenado.

+0

* No estoy obteniendo ningún golpe en msdn para ExecuteIntScalar * Maldición, lo siento! Debería haber mencionado que es un método que alguien hizo que básicamente solo llama a ** SqlCommand.ExecuteScalar() **. ¡Perdón por la confusion! – MGOwen

+0

Eso es todo! ¡A la clase Helper le faltaba un cmd.Transaction.Commit() en una de sus funciones! – MGOwen

3

Ponga el analizador de servidor sql para ver qué está sucediendo en el db.

+0

Gran idea, gracias, esto ayudó mucho (ver la actualización de la pregunta). – MGOwen

1

Podría estar reiniciando una transacción. Eso explicaría por qué la fila no está presente. No creo que proj_ins_all falle si puede observar que la variable _id está configurada. Si se lanzara una excepción, esto no sucedería. Por lo tanto, debe ser el caso de que una transacción esté en vigencia y se deshaga.

+0

* la variable _id está configurada. Si se lanzara una excepción, esto no sucedería. * Exactamente, eso es lo que pensé también, pero resulta que eso es realmente lo que estaba sucediendo. Muy extraño en mi humilde opinión (ver pregunta actualizada). – MGOwen

+0

Lo siento mal - fue la transacción después de todo (o más bien mi predecesor, que usó transacciones para algunos de sus métodos y no otros, convenciéndome de que el código que estaba usando no estaba usando una transacción, cuando en realidad era) . – MGOwen

6

O bien su transacción es no confirmada o se emite la excepción y solo se come (se ignora).

+0

¿Alguna idea de por qué podría ser ignorada? ¿Puedo activar o desactivar una variable en el servidor sql para que no oculte errores importantes? – MGOwen

+0

Lo primero es lo primero: asegúrese de comprometer la transacción. Si sigue igual, depure un código .net para ver si se lanza la excepción (durante la ejecución ... (...)). O simplemente ajuste su llamada en try-catch y registre el error. No he visto a SqlServer simplemente fallando silenciosamente y sin notificar a un cliente/llamante. Entonces, si no es un COMPROMISO, entonces lo más probable es que esté en su código .NET, y no en SQLServer. – van

+0

¿podría publicar su código .net sin embargo? – van

0

Si tiene un valor DateTime en C#, se pasará como DATETIME a SQL Server como está.

Si tiene un valor de cadena en C# que desea pasar como parámetro DATETIME, pasará por una transformación de localización vudú dependiendo de la configuración de idioma/cultura de su sistema operativo y su base de datos.

Le sugiero que se asegure de que su código realmente pase un valor DateTime (que puede verificar su corrección antes de invocar el SP).

-1

Blockquote

Error que convierte el tipo de datos varchar a fecha y hora.

Como lo estaba probando manualmente, simplemente acorté la fecha de '2009-09-03 16: 20: 11.7130000' a '2009-09-03 16:20:11' y esto corrigió el error; ahora la fila inserta bien.

Blockquote Este es un error en SQL profiler. http://support.microsoft.com/kb/974289/

Cuestiones relacionadas