So führen Sie eine .SQL-Skriptdatei mit c # aus

140

Ich bin mir sicher, dass diese Frage bereits beantwortet wurde, konnte jedoch mit dem Suchwerkzeug keine Antwort finden.

Mit c # möchte ich eine .sql-Datei ausführen. Die SQL-Datei enthält mehrere SQL-Anweisungen, von denen einige über mehrere Zeilen verteilt sind. Ich habe versucht, die Datei einzulesen und die Datei mit ODP.NET auszuführen. Ich glaube jedoch nicht, dass ExecuteNonQuery wirklich dafür ausgelegt ist.

Also habe ich versucht, sqlplus über das Laichen eines Prozesses zu verwenden. Wenn ich den Prozess jedoch nicht mit UseShellExecute erzeugt habe, das auf true gesetzt ist, würde sqlplus hängen bleiben und niemals beendet werden. Hier ist der Code, der nicht funktioniert.

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 kehrt nie zurück ... Es sei denn, ich setze UseShellExecute auf true. Ein Nebeneffekt von UseShellExecute ist, dass Sie die umgeleitete Ausgabe nicht erfassen können.

Reich
quelle
8
Hallo Herr Rich, Ihre Frage betraf Oracle und Sie haben eine Lösung für SQL Server akzeptiert? Sie haben Ihre Datenbank in SQL Server geändert?
Akshay J

Antworten:

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);
    }
}

quelle
4
Toll! Diese Lösung funktionierte für mich, um eine Datenbank löschen und neu erstellen sowie Tabellen hinzufügen zu können (über die referenzierte SQL-Skriptdatei).
Oger Psalm33
11
Diese Methode erlaubt nicht die Verwendung des Befehls "GO" in Ihrem Skript, der zulässig ist, wenn Sie ein Skript in SQL Management Studio oder den Befehl osql ausführen. msdn.microsoft.com/en-us/library/ms188037.aspx
Rn222
20
Rn222: Ich denke, Sie haben die ExecuteNonQuery-Methoden verwirrt. SqlCommand.ExecuteNonQuery erlaubt die Verwendung von "GO" -Befehlen nicht, Server.ConnectionContext.ExecuteNonQuery jedoch definitiv (ich verwende es gerade).
PeterBelm
44
Beachten Sie, dass Sie Verweise auf das Projekt zu Microsoft.SqlServer.ConnectionInfo, Microsoft.SqlServer.Management.Sdk und Microsoft.SqlServer.Smo hinzufügen müssen, damit diese Antwort funktioniert.
Thomasb
8
Für mich funktionierte es nicht, wenn ich .net 4.0 / 4.5 verwendete und auf 110 \ SDK \ Assemblies verwies. Die Lösung, die ich fand, bestand darin, die App.Config in<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup>
Abir
107

Ich habe diese Lösung mit Microsoft.SqlServer.Management ausprobiert, aber mit .NET 4.0 hat sie nicht gut funktioniert. Daher habe ich eine andere Lösung nur mit dem .NET libs-Framework geschrieben.

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
quelle
Genau. Diese Lösung schließt die Datei nicht einmal, nachdem sie verwendet wurde. Das könnte kritisch sein.
Mathias Lykkegaard Lorenzen
1
Verwenden Sie "RegexOptions.Multiline | RegexOptions.IgnoreCase", um auch die Fälle "Go" oder "go" abzugleichen.
Ankush
1
Ich denke, das RegexOptions.CultureInvariant-Flag sollte ebenfalls verwendet werden.
Dave Andersen
3
Dies funktioniert nicht zu 100%: 'GO' akzeptiert möglicherweise numerische Parameter.
Nothrow
16

Dies funktioniert unter Framework 4.0 oder höher. Unterstützt "GO". Zeigen Sie auch die Fehlermeldung, die Zeile und den SQL-Befehl an.

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
quelle
3
Netter Code, eine sehr kleine Sache ist, dass connection.Close()die Verbindung nicht benötigt wird, wird von dem geschlossen, in den usingSie sie eingewickelt haben.
Freundlich
Gute Arbeit. Es funktionierte für mich "direkt aus der Box".
Stephen85
8

Fügen Sie den Befehl zum Ausführen des SQL-Skripts in eine Batchdatei ein und führen Sie den folgenden Code aus

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 )

Schreiben Sie in die Batch-Datei so etwas (Beispiel für SQL Server)

osql -E -i %1
Binoj Antony
quelle
6

Das funktioniert bei mir:

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
quelle
4

Zusätzliche Verbesserungen zu surajits hinzugefügt Antwort:

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);
            }
        }
    }
}

Außerdem musste ich meinem Projekt folgende Verweise hinzufügen:

  • 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

Ich habe keine Ahnung, ob dies die richtigen DLLs sind, da sich in C: \ Programme \ Microsoft SQL Server mehrere Ordner befinden, aber in meiner Anwendung funktionieren diese beiden.

Der gestiefelte Kater
quelle
Dies hat bei mir in .Net 4.7 funktioniert. Die anderen von surajit erwähnten DLLs brauchte ich nicht. Ich musste jedoch Version 13.0.0.0 sowohl für Microsoft.SqlServer.ConnectionInfo als auch für Microsoft.SqlServer.Smo verwenden, da 13.100.0.0 beim Instanziieren der ServerConnection Ausnahmen auslöste.
Kevin Fichter
4

Ich habe es geschafft, die Antwort durch Lesen des Handbuchs herauszufinden :)

Dieser Auszug aus dem MSDN

Das Codebeispiel vermeidet eine Deadlock-Bedingung, indem p.StandardOutput.ReadToEnd vor p.WaitForExit aufgerufen wird. Eine Deadlock-Bedingung kann auftreten, wenn der übergeordnete Prozess p.WaitForExit vor p.StandardOutput.ReadToEnd aufruft und der untergeordnete Prozess genügend Text schreibt, um den umgeleiteten Stream zu füllen. Der übergeordnete Prozess würde unbegrenzt warten, bis der untergeordnete Prozess beendet wird. Der untergeordnete Prozess würde unbegrenzt darauf warten, dass der übergeordnete Prozess aus dem vollständigen StandardOutput-Stream liest.

Es gibt ein ähnliches Problem, wenn Sie den gesamten Text sowohl aus der Standardausgabe als auch aus den Standardfehlerströmen lesen. Der folgende C # -Code führt beispielsweise eine Leseoperation für beide Streams aus.

Verwandelt den Code in diesen;

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;
}

Welches jetzt richtig beendet.

Reich
quelle
2
Ein Tipp zu sqlplus: Wenn Sie wissen möchten, ob die Skriptausführung erfolgreich war, können Sie WHENEVER SQLERROR EXIT SQL.SQLCODE am Anfang des Skripts hinzufügen. Auf diese Weise gibt der sqlplus-Prozess die SQL-Fehlernummer als Rückkehrcode zurück.
Devdimi
ein vollständiges Beispiel für den vollständigen Quellcode? Was ist in_database, s?
Kiquenet
2
das funktioniert bei mir nicht p.StandardOutput.ReadToEnd();nie verlassen
Louis Rhys
2

Es sind zwei Punkte zu beachten.

1) Dieser Quellcode hat bei mir funktioniert:

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;
}

Ich habe das Arbeitsverzeichnis auf das Skriptverzeichnis gesetzt, damit auch Unterskripte innerhalb des Skripts funktionieren.

Nennen Sie es zB als Execute("usr/pwd@service", "c:\myscripts", "script.sql")

2) Sie müssen Ihr SQL-Skript mit der Anweisung abschließen EXIT;

StefanG
quelle
1

Mit EntityFramework können Sie eine solche Lösung wählen. Ich benutze diesen Code, um e2e-Tests zu initialisieren. Um SQL-Injection-Angriffe zu verhindern, stellen Sie sicher, dass dieses Skript nicht basierend auf Benutzereingaben generiert wird, oder verwenden Sie hierfür Befehlsparameter (siehe Überladung von ExecuteSqlCommand, das Parameter akzeptiert).

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
quelle
-1

Ich konnte keinen genauen und gültigen Weg finden, dies zu tun. Nach einem ganzen Tag kam ich mit diesem gemischten Code aus verschiedenen Quellen und versuchte, die Arbeit zu erledigen.

Es wird jedoch immer noch eine Ausnahme generiert ExecuteNonQuery: CommandText property has not been Initialized, obwohl die Skriptdatei erfolgreich ausgeführt wird. In meinem Fall wird die Datenbank erfolgreich erstellt und beim ersten Start werden Daten eingefügt.

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
quelle
Ich konnte keinen genauen und gültigen Weg finden, dies zu tun. Nach einem ganzen Tag kam ich mit diesem gemischten Code aus verschiedenen Quellen und versuchte, die Arbeit zu erledigen. Also habe ich sie alle zusammengeführt und das Ergebnis erzielt. Es wird jedoch weiterhin eine Ausnahme generiert: "ExecuteNonQuery: CommandText-Eigenschaft wurde nicht initialisiert." Obwohl die Skriptdatei erfolgreich ausgeführt wird (In meinem Fall erstellen Sie die Datenbank erfolgreich und fügen Sie beim ersten Start Daten ein).
Muhammad Salman