Come eseguire un file di script .SQL usando c #


140

Sono sicuro che questa domanda abbia già ricevuto una risposta, tuttavia non sono riuscito a trovare una risposta utilizzando lo strumento di ricerca.

Usando c # vorrei eseguire un file .sql. Il file sql contiene più istruzioni sql, alcune delle quali sono suddivise su più righe. Ho provato a leggere il file e ho provato a eseguire il file usando ODP.NET ... tuttavia non credo che ExecuteNonQuery sia davvero progettato per fare questo.

Quindi ho provato a usare sqlplus generando un processo ... tuttavia, a meno che non avessi generato il processo con UseShellExecute impostato su sqlplus vero, si sarebbe bloccato e non sarebbe mai uscito. Ecco il codice che NON FUNZIONA.

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 non restituisce mai .... A meno che non imposti UseShellExecute su true. Un effetto collaterale di UseShellExecute è che non è possibile acquisire l'output reindirizzato.


8
Ciao signor Rich, la tua domanda riguardava Oracle e hai accettato una soluzione che fosse per SQL Server? Hai cambiato il tuo DB in sql server?
Akshay J,

Risposte:


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

4
Grande! Questa soluzione ha funzionato per me per essere in grado di eliminare e ricreare un database e aggiungere tabelle (tramite il file di script SQL di riferimento).
Ogre Salmo33,

11
Questo metodo non consente l'utilizzo del comando "GO" nello script, che è consentito quando si esegue uno script da SQL Management Studio o dal comando osql. msdn.microsoft.com/en-us/library/ms188037.aspx
Rn222

20
Rn222: Penso che tu abbia confuso i metodi ExecuteNonQuery, SqlCommand.ExecuteNonQuery non ti permetterà di usare i comandi "GO", tuttavia Server.ConnectionContext.ExecuteNonQuery lo fa sicuramente (lo sto usando proprio ora).
PeterBelm,

44
Si noti che è necessario aggiungere riferimenti al progetto, a Microsoft.SqlServer.ConnectionInfo, Microsoft.SqlServer.Management.Sdk e Microsoft.SqlServer.Smo affinché questa risposta funzioni.
thomasb,

8
Per me non ha funzionato quando si utilizza .net 4.0 / 4.5, quando si fa riferimento a 110 \ SDK \ Assemblies La soluzione che ho trovato è stata modificare l'app.Config in<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup>
Abir

107

Ho provato questa soluzione con Microsoft.SqlServer.Management ma non ha funzionato bene con .NET 4.0, quindi ho scritto un'altra soluzione usando solo .NET libs framework.

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

Esattamente. Questa soluzione non chiuderà nemmeno il file dopo averlo usato. Questo potrebbe essere critico.
Mathias Lykkegaard Lorenzen

1
Utilizzare "RegexOptions.Multiline | RegexOptions.IgnoreCase" per abbinare anche i casi "Go" o "go".
Ankush,

1
Penso che anche la bandiera RegexOptions.CultureInvariant dovrebbe essere usata.
Dave Andersen,

3
Questo non funziona al 100%: 'GO' può accettare parametri numerici.
nothrow

16

Funziona su Framework 4.0 o versione successiva. Supporta "GO". Mostra anche il messaggio di errore, la riga e il comando 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;
        }
    }

3
Bel codice, una cosa molto minore è che non è necessario che connection.Close()la connessione venga chiusa dal momento in cui l' usinghai avvolta.
Amico

Ottimo lavoro. Ha funzionato "immediatamente fuori dagli schemi" per me.
Stephen85,

8

Inserire il comando per eseguire lo script sql in un file batch, quindi eseguire il codice seguente

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 )

nel file batch scrivere qualcosa di simile (esempio per SQL Server)

osql -E -i %1

6

Questo funziona per me:

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

4

Aggiunti ulteriori miglioramenti alla risposta di 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);
            }
        }
    }
}

Inoltre, ho dovuto aggiungere i seguenti riferimenti al mio progetto:

  • 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

Non ho idea se quelli siano i dll: s giusti da usare poiché ci sono diverse cartelle in C: \ Programmi \ Microsoft SQL Server ma nella mia applicazione questi due funzionano.


Questo ha funzionato per me in .Net 4.7. Non avevo bisogno delle altre DLL menzionate da Surajit. Tuttavia, ho dovuto utilizzare la versione 13.0.0.0 sia per Microsoft.SqlServer.ConnectionInfo che per Microsoft.SqlServer.Smo, poiché 13.100.0.0 ha generato eccezioni durante l'istanza di ServerConnection.
Kevin Fichter,

4

Sono riuscito a elaborare la risposta leggendo il manuale :)

Questo estratto da MSDN

L'esempio di codice evita una condizione di deadlock chiamando p.StandardOutput.ReadToEnd prima di p.WaitForExit. Può verificarsi una condizione di deadlock se il processo padre chiama p.WaitForExit prima di p.StandardOutput.ReadToEnd e il processo figlio scrive una quantità di testo sufficiente per riempire il flusso reindirizzato. Il processo genitore aspetterebbe indefinitamente l'arresto del processo figlio. Il processo figlio aspetterebbe indefinitamente che il genitore legga dal flusso StandardOutput completo.

Esiste un problema simile quando si legge tutto il testo sia dall'output standard sia dai flussi di errore standard. Ad esempio, il seguente codice C # esegue un'operazione di lettura su entrambi i flussi.

Trasforma il codice in questo;

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

Che ora esce correttamente.


2
Un consiglio per quanto riguarda sqlplus: se vuoi sapere se l'esecuzione dello script è andata a buon fine puoi aggiungere WHENEVER SQLERROR EXIT SQL.SQLCODE all'inizio dello script. In questo modo il processo sqlplus restituisce il numero di errore sql come codice di ritorno.
devdimi,

qualche esempio completo di codice sorgente completo? cos'è in_database, s ??
Kiquenet,

2
questo non funziona per me. p.StandardOutput.ReadToEnd();non esce mai
Louis Rhys,

2

Ci sono due punti da considerare.

1) Questo codice sorgente ha funzionato per me:

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

Ho impostato la directory di lavoro sulla directory degli script, in modo che anche gli script secondari all'interno dello script funzionino.

Chiamalo ad es. Come Execute("usr/pwd@service", "c:\myscripts", "script.sql")

2) Devi finalizzare il tuo script SQL con la dichiarazione EXIT;


1

Usando EntityFramework, puoi scegliere una soluzione come questa. Uso questo codice per inizializzare i test e2e. Per evitare attacchi di iniezione sql, assicurarsi di non generare questo script in base all'input dell'utente o utilizzare i parametri di comando per questo (vedere sovraccarico di ExecuteSqlCommand che accetta i parametri).

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

-1

Non sono riuscito a trovare un modo esatto e valido per farlo. Quindi, dopo un'intera giornata, sono arrivato con questo codice misto ottenuto da diverse fonti e cercando di fare il lavoro.

Ma sta ancora generando un'eccezione ExecuteNonQuery: CommandText property has not been Initializedanche se esegue correttamente il file di script - nel mio caso, crea correttamente il database e inserisce i dati al primo avvio.

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
        {

        }

    }

}

Non sono riuscito a trovare un modo esatto e valido per farlo. Quindi, dopo un'intera giornata, sono arrivato con questo codice misto ottenuto da diverse fonti e cercando di fare il lavoro. così li ho uniti tutti e ne ho ricavato il risultato. Ma genera ancora un'eccezione "La proprietà ExecuteNonQuery: CommandText non è stata inizializzata." Sebbene esegua correttamente il file di script (nel mio caso, creare correttamente il database e inserire i dati al primo avvio).
Muhammad Salman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.