Jak wykonać plik skryptu SQL przy użyciu języka C #

140

Jestem pewien, że na to pytanie już udzielono odpowiedzi, jednak nie mogłem znaleźć odpowiedzi za pomocą narzędzia wyszukiwania.

Używając C #, chciałbym uruchomić plik .sql. Plik sql zawiera wiele instrukcji sql, z których niektóre są podzielone na wiele wierszy. Próbowałem wczytać plik i spróbowałem uruchomić plik za pomocą ODP.NET ... jednak nie sądzę, aby funkcja ExecuteNonQuery była naprawdę zaprojektowana do tego.

Więc próbowałem użyć sqlplus poprzez tworzenie procesu ... jednak jeśli nie utworzyłem procesu z UseShellExecute ustawionym na true sqlplus, zawiesiłoby się i nigdy nie zakończył. Oto kod, który NIE DZIAŁA.

Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "sqlplus";
p.StartInfo.Arguments = string.Format("xx/xx@{0} @{1}", in_database, s);
p.StartInfo.CreateNoWindow = true;

bool started = p.Start();
p.WaitForExit();

WaitForExit nigdy nie zwraca .... Chyba że ustawię UseShellExecute na true. Efektem ubocznym UseShellExecute jest brak możliwości przechwycenia przekierowanych danych wyjściowych.

Bogaty
źródło
8
Witam panie Rich, Pana pytanie dotyczyło Oracle i zaakceptował Pan rozwiązanie przeznaczone dla serwera sql? Zmieniłeś swoją bazę danych na serwer sql?
Akshay J,

Odpowiedzi:

185
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using System.IO;
using System.Data.SqlClient;

public partial class ExcuteScript : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    string sqlConnectionString = @"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=ccwebgrity;Data Source=SURAJIT\SQLEXPRESS";

    string script = File.ReadAllText(@"E:\Project Docs\MX462-PD\MX756_ModMappings1.sql");

    SqlConnection conn = new SqlConnection(sqlConnectionString);

    Server server = new Server(new ServerConnection(conn));

    server.ConnectionContext.ExecuteNonQuery(script);
    }
}

źródło
4
Wspaniały! To rozwiązanie zadziałało w moim przypadku, ponieważ mogłem upuszczać i odtwarzać bazę danych oraz dodawać tabele (za pośrednictwem wskazanego pliku skryptu SQL).
Ogre Psalm 33
11
Ta metoda nie pozwala na użycie polecenia „GO” w skrypcie, które jest dozwolone w przypadku uruchamiania skryptu z programu SQL Management Studio lub polecenia osql. msdn.microsoft.com/en-us/library/ms188037.aspx
Rn222,
20
Rn222: Myślę, że pomyliłeś metody ExecuteNonQuery, SqlCommand.ExecuteNonQuery nie pozwoli na użycie poleceń "GO", jednak Server.ConnectionContext.ExecuteNonQuery zdecydowanie tak (używam go w tej chwili).
PeterBelm
44
Zauważ, że aby ta odpowiedź zadziałała, musisz dodać odwołania do projektu, do Microsoft.SqlServer.ConnectionInfo, Microsoft.SqlServer.Management.Sdk i Microsoft.SqlServer.Smo.
thomasb
8
Dla mnie to nie zadziałało, gdy korzystałem z .net 4.0 / 4.5, podczas odwoływania się do 110 \ SDK \ Assemblies. Rozwiązaniem, które znalazłem, była zmiana aplikacji.Config na<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup>
Abir
107

Wypróbowałem to rozwiązanie z Microsoft.SqlServer.Management, ale nie działało dobrze z .NET 4.0, więc napisałem inne rozwiązanie wykorzystujące tylko framework .NET libs.

string script = File.ReadAllText(@"E:\someSqlScript.sql");

// split script on GO command
IEnumerable<string> commandStrings = Regex.Split(script, @"^\s*GO\s*$", RegexOptions.Multiline | RegexOptions.IgnoreCase);

Connection.Open();
foreach (string commandString in commandStrings)
{
    if (!string.IsNullOrWhiteSpace(commandString.Trim()))
    {
        using(var command = new SqlCommand(commandString, Connection))
        {
            command.ExecuteNonQuery();
        }
    }
}     
Connection.Close();
Hacko
źródło
Dokładnie. To rozwiązanie nawet nie zamknie pliku po jego użyciu. To może być krytyczne.
Mathias Lykkegaard Lorenzen
1
Użyj „RegexOptions.Multiline | RegexOptions.IgnoreCase”, aby dopasować również przypadki „Go” lub „go”.
Ankush
1
Myślę, że należy również użyć flagi RegexOptions.CultureInvariant.
Dave Andersen
3
To nie działa w 100%: „GO” może akceptować parametr numeryczny.
nothrow
16

Działa to na platformie Framework 4.0 lub nowszej. Obsługuje „GO”. Pokaż także komunikat o błędzie, wiersz i polecenie sql.

using System.Data.SqlClient;

        private bool runSqlScriptFile(string pathStoreProceduresFile, string connectionString)
    {
        try
        {
            string script = File.ReadAllText(pathStoreProceduresFile);

            // split script on GO command
            System.Collections.Generic.IEnumerable<string> commandStrings = Regex.Split(script, @"^\s*GO\s*$",
                                     RegexOptions.Multiline | RegexOptions.IgnoreCase);
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();
                foreach (string commandString in commandStrings)
                {
                    if (commandString.Trim() != "")
                    {
                        using (var command = new SqlCommand(commandString, connection))
                        {
                        try
                        {
                            command.ExecuteNonQuery();
                        }
                        catch (SqlException ex)
                        {
                            string spError = commandString.Length > 100 ? commandString.Substring(0, 100) + " ...\n..." : commandString;
                            MessageBox.Show(string.Format("Please check the SqlServer script.\nFile: {0} \nLine: {1} \nError: {2} \nSQL Command: \n{3}", pathStoreProceduresFile, ex.LineNumber, ex.Message, spError), "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                            return false;
                        }
                    }
                    }
                }
                connection.Close();
            }
        return true;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            return false;
        }
    }
Xtian11
źródło
3
Niezły kod, jedną bardzo drobną rzeczą jest to, że nie potrzebuje connection.Close()połączenia, zostanie zamknięte przez to using, w co go zapakowałeś.
Polubowny
Świetna robota. U mnie zadziałało „od razu po wyjęciu z pudełka”.
Stephen85
8

Umieść polecenie wykonania skryptu sql w pliku wsadowym, a następnie uruchom poniższy kod

string batchFileName = @"c:\batosql.bat";
string sqlFileName = @"c:\MySqlScripts.sql";
Process proc = new Process();
proc.StartInfo.FileName = batchFileName;
proc.StartInfo.Arguments = sqlFileName;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.ErrorDialog = false;
proc.StartInfo.WorkingDirectory = Path.GetDirectoryName(batchFileName);
proc.Start();
proc.WaitForExit();
if ( proc.ExitCode!= 0 )

w pliku wsadowym napisz coś takiego (przykład dla serwera sql)

osql -E -i %1
Binoj Antony
źródło
6

To działa dla mnie:

public void updatedatabase()
{

    SqlConnection conn = new SqlConnection("Data Source=" + txtserver.Text.Trim() + ";Initial Catalog=" + txtdatabase.Text.Trim() + ";User ID=" + txtuserid.Text.Trim() + ";Password=" + txtpwd.Text.Trim() + "");
    try
    {

        conn.Open();

        string script = File.ReadAllText(Server.MapPath("~/Script/DatingDemo.sql"));

        // split script on GO command
        IEnumerable<string> commandStrings = Regex.Split(script, @"^\s*GO\s*$", RegexOptions.Multiline | RegexOptions.IgnoreCase);
        foreach (string commandString in commandStrings)
        {
            if (commandString.Trim() != "")
            {
                new SqlCommand(commandString, conn).ExecuteNonQuery();
            }
        }
        lblmsg.Text = "Database updated successfully.";

    }
    catch (SqlException er)
    {
        lblmsg.Text = er.Message;
        lblmsg.ForeColor = Color.Red;
    }
    finally
    {
        conn.Close();
    }
}
Neelam saini
źródło
4

Dodano dodatkowe ulepszenia do odpowiedzi surajits:

using System;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Common;
using System.IO;
using System.Data.SqlClient;

namespace MyNamespace
{
    public partial class RunSqlScript : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            var connectionString = @"your-connection-string";
            var pathToScriptFile = Server.MapPath("~/sql-scripts/") + "sql-script.sql";
            var sqlScript = File.ReadAllText(pathToScriptFile);

            using (var connection = new SqlConnection(connectionString))
            {
                var server = new Server(new ServerConnection(connection));
                server.ConnectionContext.ExecuteNonQuery(sqlScript);
            }
        }
    }
}

Musiałem również dodać następujące odniesienia do mojego projektu:

  • C:\Program Files\Microsoft SQL Server\120\SDK\Assemblies\Microsoft.SqlServer.ConnectionInfo.dll
  • C:\Program Files\Microsoft SQL Server\120\SDK\Assemblies\Microsoft.SqlServer.Smo.dll

Nie mam pojęcia, czy są to odpowiednie biblioteki dll: s, ponieważ w C: \ Program Files \ Microsoft SQL Server znajduje się kilka folderów, ale w mojej aplikacji te dwa działają.

Kot w butach
źródło
To zadziałało dla mnie w .Net 4.7. Nie potrzebowałem innych bibliotek DLL wymienionych przez surajit. Musiałem jednak użyć wersji 13.0.0.0 zarówno dla Microsoft.SqlServer.ConnectionInfo, jak i Microsoft.SqlServer.Smo, ponieważ 13.100.0.0 generowało wyjątki podczas tworzenia instancji ServerConnection.
Kevin Fichter
4

Udało mi się wypracować odpowiedź, czytając instrukcję :)

Ten wyciąg z MSDN

Przykładowy kod pozwala uniknąć zakleszczenia, wywołując p.StandardOutput.ReadToEnd przed p.WaitForExit. Zakleszczenie może wystąpić, jeśli proces nadrzędny wywoła p.WaitForExit przed p.StandardOutput.ReadToEnd, a proces potomny zapisze wystarczającą ilość tekstu, aby wypełnić przekierowany strumień. Proces nadrzędny czekałby w nieskończoność na zakończenie procesu potomnego. Proces potomny czekałby w nieskończoność na odczytanie przez rodzica z pełnego strumienia StandardOutput.

Podobny problem występuje podczas odczytywania całego tekstu zarówno ze standardowego wyjścia, jak i standardowego strumienia błędów. Na przykład poniższy kod C # wykonuje operację odczytu na obu strumieniach.

Zamienia kod w to;

Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "sqlplus";
p.StartInfo.Arguments = string.Format("xxx/xxx@{0} @{1}", in_database, s);

bool started = p.Start();
// important ... read stream input before waiting for exit.
// this avoids deadlock.
string output = p.StandardOutput.ReadToEnd();

p.WaitForExit();

Console.WriteLine(output);

if (p.ExitCode != 0)
{
    Console.WriteLine( string.Format("*** Failed : {0} - {1}",s,p.ExitCode));
    break;
}

Który teraz wychodzi poprawnie.

Bogaty
źródło
2
Wskazówka dotycząca sqlplus: jeśli chcesz wiedzieć, czy wykonanie skryptu powiodło się, możesz dodać WHENEVER SQLERROR EXIT SQL.SQLCODE na początku skryptu. W ten sposób proces sqlplus zwraca numer błędu sql jako kod powrotu.
devdimi,
jakikolwiek kompletny przykładowy kod źródłowy? co to jest in_database, s?
Kiquenet
2
to nie działa dla mnie. p.StandardOutput.ReadToEnd();nigdy nie wychodzi
Louis Rhys
2

Należy wziąć pod uwagę dwie kwestie.

1) Ten kod źródłowy działał dla mnie:

private static string Execute(string credentials, string scriptDir, string scriptFilename)
{ 
  Process process = new Process();
  process.StartInfo.UseShellExecute = false;
  process.StartInfo.WorkingDirectory = scriptDir;
  process.StartInfo.RedirectStandardOutput = true;
  process.StartInfo.FileName = "sqlplus";
  process.StartInfo.Arguments = string.Format("{0} @{1}", credentials, scriptFilename);
  process.StartInfo.CreateNoWindow = true;

  process.Start();
  string output = process.StandardOutput.ReadToEnd();
  process.WaitForExit();

  return output;
}

Ustawiłem katalog roboczy na katalog skryptów, aby działały również skrypty podrzędne w skrypcie.

Nazwij to np. Jako Execute("usr/pwd@service", "c:\myscripts", "script.sql")

2) Musisz sfinalizować skrypt SQL za pomocą instrukcji EXIT;

StefanG
źródło
1

Korzystając z EntityFramework, możesz skorzystać z takiego rozwiązania. Używam tego kodu do inicjalizacji testów e2e. Aby zapobiec atakom sql injection, upewnij się, że nie generujesz tego skryptu na podstawie danych wejściowych użytkownika ani nie używaj w tym celu parametrów polecenia (zobacz przeciążenie ExecuteSqlCommand, który akceptuje parametry).

public static void ExecuteSqlScript(string sqlScript)
{
    using (MyEntities dataModel = new MyEntities())
    {
        // split script on GO commands
        IEnumerable<string> commands = 
            Regex.Split(
                sqlScript, 
                @"^\s*GO\s*$",
                RegexOptions.Multiline | RegexOptions.IgnoreCase);

        foreach (string command in commands)
        {
            if (command.Trim() != string.Empty)
            {
                dataModel.Database.ExecuteSqlCommand(command);
            }
        }              
    }
}
martinoss
źródło
-1

Nie mogłem znaleźć żadnego dokładnego i ważnego sposobu, aby to zrobić. Więc po całym dniu przyszedłem z tym mieszanym kodem uzyskanym z różnych źródeł i próbując wykonać zadanie.

Ale nadal generuje wyjątek, ExecuteNonQuery: CommandText property has not been Initializedmimo że pomyślnie uruchamia plik skryptu - w moim przypadku pomyślnie tworzy bazę danych i wstawia dane przy pierwszym uruchomieniu.

public partial class Form1 : MetroForm
{
    SqlConnection cn;
    SqlCommand cm;
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        if (!CheckDatabaseExist())
        {
            GenerateDatabase();
        }
    }

    private bool CheckDatabaseExist()
    {
        SqlConnection con = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=SalmanTradersDB;Integrated Security=true");
        try
        {
            con.Open();
            return true;
        }
        catch
        {
            return false;
        }
    }

    private void GenerateDatabase()
    {

        try
        {
            cn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=master;Integrated Security=True");
            StringBuilder sb = new StringBuilder();
            sb.Append(string.Format("drop databse {0}", "SalmanTradersDB"));
            cm = new SqlCommand(sb.ToString() , cn);
            cn.Open();
            cm.ExecuteNonQuery();
            cn.Close();
        }
        catch
        {

        }
        try
        {
            //Application.StartupPath is the location where the application is Installed
            //Here File Path Can Be Provided Via OpenFileDialog
            if (File.Exists(Application.StartupPath + "\\script.sql"))
            {
                string script = null;
                script = File.ReadAllText(Application.StartupPath + "\\script.sql");
                string[] ScriptSplitter = script.Split(new string[] { "GO" }, StringSplitOptions.None);
                using (cn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=master;Integrated Security=True"))
                {
                    cn.Open();
                    foreach (string str in ScriptSplitter)
                    {
                        using (cm = cn.CreateCommand())
                        {
                            cm.CommandText = str;
                            cm.ExecuteNonQuery();
                        }
                    }
                }
            }
        }
        catch
        {

        }

    }

}
Muhammad Salman
źródło
Nie mogłem znaleźć żadnego dokładnego i ważnego sposobu, aby to zrobić. Więc po całym dniu przyszedłem z tym mieszanym kodem uzyskanym z różnych źródeł i próbując wykonać zadanie. więc połączyłem je wszystkie i uzyskałem wynik. Ale nadal generuje wyjątek „ExecuteNonQuery: Właściwość CommandText nie została zainicjowana”. Chociaż to Pomyślnie uruchamia plik skryptu (w moim przypadku pomyślnie utwórz bazę danych i wstaw dane przy pierwszym uruchomieniu).
Muhammad Salman