2010-03-16 31 views
16

Estoy creando una utilidad de despliegue db personalizada, necesito leer archivos de texto que contengan scripts sql y ejecutarlos en la base de datos.SqlCommand() ExecuteNonQuery() trunca el texto del comando

Cosas bastante fáciles, hasta ahora todo bien.

Sin embargo, he encontrado un inconveniente, el contenido del archivo se lee con éxito y completamente, pero una vez pasado al SqlCommand y luego se ejecuta con SqlCommand.ExecuteNonQuery, solo se ejecuta parte del script.

Encendí Profiler y confirmé que mi código no está pasando todo el script.

private void ExecuteScript(string cmd, SqlConnection sqlConn, SqlTransaction trans) 
    { 

     SqlCommand sqlCmd = new SqlCommand(cmd, sqlConn, trans); 
     sqlCmd.CommandType = CommandType.Text; 
     sqlCmd.CommandTimeout = 9000000; // for testing 
     sqlCmd.ExecuteNonQuery(); 

    } 

    // I call it like this, readDMLScript contains 543 lines of T-SQL 
    string readDMLScript = ReadFile(dmlFile); 
    ExecuteScript(readDMLScript, sqlConn, trans); 
+1

¿Qué personaje es el guión truncado? – MikeWyatt

+1

¿Cómo funciona el método 'ReadFile'? ¿Estás 200% seguro de que no se está saltando algunos personajes, quizás? ¿Por qué no usar 'System.IO.File.ReadAllText (filename)' ?? –

+0

cómo * mucho * texto estás leyendo del archivo, en bytes? –

Respuesta

35

Sí, todos llegan a este inconveniente la primera vez que comienzan a enviar los contenidos de los archivos de script SQL al base de datos.

GO no es un comando de T-SQL. Es el marcador de fin de lote reconocido por todas las herramientas SQL interactivas de Microsoft (Management Studio, isql, osql). Para manejarlo, tendrá que escribir su propio analizador para dividir cada bloque de texto en el archivo entre las declaraciones GO y alimentarlas a la base de datos como comandos separados.

La manera de implementar su analizador depende de usted. Podría ser simple (leer en cada línea a la vez, detectar líneas que consisten en nada más que GO y espacios en blanco) o complejo (simbolizar todas las declaraciones y determinar si un GO es una declaración genuina o un fragmento de texto dentro de una cadena o comentario de varias líneas).

Personalmente fui con la primera opción. Maneja el 99% de todos los archivos SQL que es probable que encuentres sin problemas. Si quieres ir a por todas y escribir un tokeniser, estoy seguro de que mucha gente ya lo hizo, solo busca Google.

Ejemplo:

using(var reader = new SqlBatchReader(new StreamReader(dmlFile))) { 
    string batch; 
    while((batch = reader.ReadBatch()) != null) { 
     var cmd = new SqlCommand(batch, conn, trans) { CommandType = CommandType.Text }; 
     cmd.ExecuteNonQuery(); 
    } 
} 

class SqlBatchReader : IDisposable { 
    private TextReader _reader; 
    public SqlBatchReader(TextReader reader) { 
     _reader = reader; 
    } 
    /// <summary> 
    /// Return the next command batch in the file, or null if end-of-file reached. 
    /// </summary> 
    public string ReadBatch() { 
     // TODO: Implement your parsing logic here. 
    } 
} 
+0

A menos que el archivo de script comience con 'SET IDENTITY_INSERT ON'. ¿No hay una buena solución para exportar datos a un archivo para .NET y luego importar? – MStodd

+0

@MStodd: 'SET IDENTITY_INSERT' está destinado para su uso en scripts de importación de datos, por lo que es una buena solución. Personalmente, sin embargo, usaría SSIS para importar datos. –

+0

El problema que tengo ahora es si exporto un script de SSMS comenzando con 'SET IDENTITY_INSERT ON', no será suficiente usar tu código, ¿verdad? ¿No tendré que ejecutar ese comando antes de cada inserción? – MStodd

1

respuesta basada en los comentarios bajo el post original:

GO es un marcador para el estudio de la gerencia/osql/isql. Le dice que envíe un lote de comandos a SQL Server. En su utilidad, debe dividir los datos de entrada usando GO como delimitador y enviar cada elemento individualmente (sin el comando GO)

0

Esto es lo que utilizamos :)

public static class ExtensionMethodsSqlCommand 
{ 
    #region Public 

    private static bool IsGo(string psCommandLine) 
    { 
     if (psCommandLine == null) 
      return false; 
     psCommandLine = psCommandLine.Trim(); 
     if (string.Compare(psCommandLine, "GO", StringComparison.OrdinalIgnoreCase) == 0) 
      return true; 
     if (psCommandLine.StartsWith("GO", StringComparison.OrdinalIgnoreCase)) 
     { 
      psCommandLine = (psCommandLine + "--").Substring(2).Trim(); 
      if (psCommandLine.StartsWith("--")) 
       return true; 
     } 
     return false; 
    } 

    [System.Diagnostics.DebuggerHidden] 
    public static void ExecuteNonQueryWithGos(this SqlCommand poSqlCommand) 
    { 
     string sCommandLong = poSqlCommand.CommandText; 
     using (StringReader oStringReader = new StringReader(sCommandLong)) 
     { 
      string sCommandLine; 
      string sCommandShort = string.Empty; 
      while ((sCommandLine = oStringReader.ReadLine()) != null) 
       if (ExtensionMethodsSqlCommand.IsGo(sCommandLine)) 
       { 
        if (sCommandShort.IsNullOrWhiteSpace() == false) 
        { 
         if ((poSqlCommand.Connection.State & ConnectionState.Open) == 0) 
          poSqlCommand.Connection.Open(); 
         using (SqlCommand oSqlCommand = new SqlCommand(sCommandShort, poSqlCommand.Connection)) 
          oSqlCommand.ExecuteNonQuery(); 
        } 
        sCommandShort = string.Empty; 
       } 
       else 
        sCommandShort += sCommandLine + "\r\n"; 
     } 
    } 

    #endregion Public 
} 
+0

¿Por qué no agrega alguna descripción del ejemplo de código que ha publicado? Es más fácil leer la descripción y comprender que la solución funciona o no funciona para usted, que dedicar un tiempo a analizar lo que está haciendo el código. – Artemix

5

He encontrado el código de abajo mientras que la búsqueda para una respuesta a este problema:

http://blogs.msdn.com/b/onoj/archive/2008/02/26/incorrect-syntax-near-go-sqlcommand-executenonquery.aspx

Pros: Es corto y sencillo de entender y funcionaba perfectamente para mis necesidades.

Contras: Es menos eficiente que las soluciones basadas en Stream y distingue entre mayúsculas y minúsculas (es decir, "GO" no "go").

string[] commands = sql.Split(new string[]{"GO\r\n", "GO ", "GO\t"}, StringSplitOptions.RemoveEmptyEntries); 
foreach (string c in commands) 
{ 
    var command = new SqlCommand(c, masterConnection); 
    command.ExecuteNonQuery(); 
} 
0

Terminé escribiendo una implementación de un StringReader para hacer esto.

Maneja:

  1. Saltarse el pasado GO contenida en el tablero de instrumentos comenta
  2. Saltarse el pasado IR contenido en estrella barra comenta
  3. Saltarse el pasado ir contenida en los literales (es decir, comillas simples)
  4. Saltarse pasado GO contenido en nombres de columnas, etc.

Por lo tanto, solo detectará la palabra clave GO whe n se usa como un separador de lotes. Esto significa que divide el texto SQL correctamente.

También se ocupa de si se ha anexado un terminador de SQL (coma) para el primer momento

Puede encontrar el código para ello here:

Se utiliza este modo:

using (var reader = new SqlCommandReader(scriptContents)) 
     { 
      var commands = new List<string>(); 
      reader.ReadAllCommands(c => commands.Add(c)); 
      // commands now contains each seperated sql batch. 
     } 
Cuestiones relacionadas