2010-03-16 10 views
16

Buduję niestandardowe narzędzie do wdrażania bazy danych, muszę odczytać pliki tekstowe zawierające skrypty sql i wykonać je w bazie danych.SqlCommand() ExecuteNonQuery() obcina tekst polecenia

Bardzo łatwe rzeczy, na razie tak dobrze.

Jednak spotkałem się z zakleszczeniem, zawartość pliku jest odczytywana pomyślnie i całkowicie, ale po przejściu do SqlCommand, a następnie wykonaniu z SqlCommand.ExecuteNonQuery wykonywana jest tylko część skryptu.

Zainicjowałem program Profiler i potwierdziłem, że mój kod nie przekazuje całego skryptu.

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

Na jakiej postaci skrócił się skrypt? – MikeWyatt

+1

Jak działa metoda 'ReadFile'? Czy jesteś 200% pewny, że nie omija kilku postaci, może? Dlaczego nie po prostu użyć 'System.IO.File.ReadAllText (filename)' ?? –

+0

jak * dużo * tekst czytasz z pliku, w bajtach? –

Odpowiedz

35

Tak, każdy trafia w ten szkopuł przy pierwszym wysyłaniu zawartości plików skryptu SQL do Baza danych.

GO nie jest poleceniem T-SQL. Jest to marker końca partii rozpoznawany przez wszystkie interaktywne narzędzia SQL firmy Microsoft (Management Studio, isql, osql). Aby sobie z nim poradzić, będziesz musiał napisać własny parser, aby wydzielić każdy blok tekstu w pliku pomiędzy instrukcjami GO i przekazać je do bazy danych jako oddzielne polecenia.

Sposób wdrożenia swojego parsera należy do Ciebie. Może to być proste (czytać w każdym wierszu na raz, wykrywać linie składające się wyłącznie z GO i białych znaków) lub złożone (tokenizować wszystkie instrukcje i sprawdzać, czy GO jest oryginalną instrukcją lub fragmentem tekstu w ciągu znaków lub komentarz wielowierszowy).

Osobiście wybrałem opcję pierwszą. Obsługuje 99% wszystkich plików SQL, z którymi możesz się zetknąć bez problemów. Jeśli chcesz pójść na całość i napisać tokenisera, jestem pewien, że wiele osób już to zrobiło, po prostu Google.

Przykład:

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

O ile plik skryptu nie zaczyna się od "SET IDENTITY_INSERT ON". Czy nie istnieje dobre rozwiązanie do eksportowania danych do pliku .NET do następnie importowania? – MStodd

+0

@MStodd: 'SET IDENTITY_INSERT' jest przeznaczony do użycia w skrypcie importu danych, więc jest dobrym rozwiązaniem. Osobiście jednak użyłbym SSIS do importowania danych. –

+0

Mam teraz problem, jeśli wyeksportuję skrypt z SSMS, zaczynając od "SET IDENTITY_INSERT ON", to nie wystarczy, że użyjesz swojego kodu? Czy nie będę musiał wykonywać tego polecenia przed każdym wstawieniem? – MStodd

1

odpowiedzi na podstawie uwag pod oryginalnym post:

GO jest markerem Management Studio/osql/isql. Mówi, aby wysłać pakiet poleceń do SQL Server. W twoim narzędziu, powinieneś podzielić dane wejściowe używając GO jako ogranicznika i wysłać każdy element indywidualnie (bez polecenia GO).

0

to co używamy :)

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

Dlaczego nie dodasz opisu kodu, który opublikowałeś? Łatwiej jest przeczytać opis i zrozumieć, że rozwiązanie działa lub nie działa dla Ciebie, niż spędzić trochę czasu na analizowanie działania kodu. – Artemix

5

znalazłem poniższy kod podczas poszukiwania w celu uzyskania odpowiedzi na ten numer:

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

Zalety: Jest krótki i prosty w zrozumieniu i działa idealnie dla moich potrzeb.

Wady: Jest mniej wydajny niż rozwiązania oparte na strumieniu i rozróżnia małe i duże litery (tzn. "GO" nie "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

Skończyłem na pisaniu implementacji StringReadera, aby to zrobić.

Zajmuje:

  1. Pomijanie przeszłość GO zawarte w desce rozdzielczej desce rozdzielczej komentuje
  2. Pomijanie przeszłość GO zawarte w gwiazdę slash komentuje
  3. Pomijanie przeszłość GO zawarte w literały (tj pojedyncze cudzysłowy)
  4. Pomijanie przeszłości GO zawarte w nazwach kolumn itp.

Dlatego wykrywa tylko słowo kluczowe n używany jako separator wsadowy. Oznacza to, że poprawnie dzieli tekst SQL.

Zajmuje się także, jeśli dołączone do sql terminator (średnik) do słowa GO

można znaleźć kod dla niego here:

go używać tak:

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