Cómo ejecutar un archivo de script .SQL usando c #

140

Estoy seguro de que esta pregunta ya ha sido respondida, sin embargo, no pude encontrar una respuesta usando la herramienta de búsqueda.

Usando c # me gustaría ejecutar un archivo .sql. El archivo sql contiene varias declaraciones sql, algunas de las cuales están divididas en varias líneas. Intenté leer el archivo e intenté ejecutar el archivo usando ODP.NET ... sin embargo, no creo que ExecuteNonQuery esté realmente diseñado para hacer esto.

Así que intenté usar sqlplus para generar un proceso ... sin embargo, a menos que haya generado el proceso con UseShellExecute establecido en verdadero, sqlplus se colgaría y nunca saldría. Aquí está el código que NO FUNCIONA.

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 nunca regresa ... A menos que establezca UseShellExecute en verdadero. Un efecto secundario de UseShellExecute es que no puede capturar la salida redirigida.

Rico
fuente
8
Hola, Sr. Rich, ¿su pregunta era sobre Oracle y aceptó una solución para el servidor SQL? ¿Cambiaste tu DB al servidor sql?
Akshay J

Respuestas:

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

fuente
44
¡Excelente! Esta solución me funcionó para poder soltar y recrear una base de datos y agregar tablas (a través del archivo de script SQL referenciado).
Ogre Psalm33
11
Este método no permite usar el comando "IR" en su secuencia de comandos que se permite cuando ejecuta una secuencia de comandos desde SQL Management Studio o el comando osql. msdn.microsoft.com/en-us/library/ms188037.aspx
Rn222
20
Rn222: Creo que has confundido los métodos ExecuteNonQuery, SqlCommand.ExecuteNonQuery no permitirá el uso de comandos "GO", sin embargo Server.ConnectionContext.ExecuteNonQuery definitivamente lo hace (lo estoy usando ahora).
PeterBelm
44
Tenga en cuenta que debe agregar referencias al proyecto, a Microsoft.SqlServer.ConnectionInfo, Microsoft.SqlServer.Management.Sdk y Microsoft.SqlServer.Smo para que esta respuesta funcione.
thomasb
8
Para mí, no funcionó al usar .net 4.0 / 4.5, al hacer referencia a 110 \ SDK \ Assemblies La solución que encontré fue cambiar la aplicación. Configurar a<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup>
Abir
107

Probé esta solución con Microsoft.SqlServer.Management pero no funcionó bien con .NET 4.0, así que escribí otra solución 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();
Hacko
fuente
Exactamente. Esta solución ni siquiera cerrará el archivo una vez que haya terminado de usarlo. Eso podría ser crítico.
Mathias Lykkegaard Lorenzen
1
Utilice "RegexOptions.Multiline | RegexOptions.IgnoreCase" para hacer coincidir los casos "Go" o "go" también.
Ankush
1
Creo que el indicador RegexOptions.CultureInvariant también debería usarse.
Dave Andersen
3
Esto no funciona al 100%: 'GO' puede aceptar parámetros numéricos.
nothrow
16

Esto funciona en Framework 4.0 o superior. Soporta "IR". También muestre el mensaje de error, la línea y el 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;
        }
    }
Xtian11
fuente
3
Código agradable, una cosa muy menor es que no se necesita connection.Close()la conexión se cerrará por la usingque ha envolvió en.
amistosa
Buen trabajo. Funcionó "directamente de la caja" para mí.
Stephen85
8

Ponga el comando para ejecutar el script sql en un archivo por lotes y luego ejecute el siguiente código

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 )

en el archivo por lotes escriba algo como esto (muestra para el servidor sql)

osql -E -i %1
Binoj Antony
fuente
6

Esto funciona para mi:

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

Se agregaron mejoras adicionales a la respuesta de 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);
            }
        }
    }
}

Además, tuve que agregar las siguientes referencias a mi proyecto:

  • 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

No tengo idea de si esos son los dll: s adecuados para usar, ya que hay varias carpetas en C: \ Archivos de programa \ Microsoft SQL Server, pero en mi aplicación estos dos funcionan.

El gato con botas
fuente
Esto funcionó para mí en .Net 4.7. No necesitaba los otros dlls mencionados por surajit. Sin embargo, tuve que usar la versión 13.0.0.0 para Microsoft.SqlServer.ConnectionInfo y Microsoft.SqlServer.Smo, ya que 13.100.0.0 arrojó excepciones al crear instancias de ServerConnection.
Kevin Fichter
4

Logré encontrar la respuesta leyendo el manual :)

Este extracto del MSDN

El ejemplo de código evita una condición de punto muerto llamando a p.StandardOutput.ReadToEnd antes de p.WaitForExit. Puede producirse una condición de punto muerto si el proceso primario llama a p.WaitForExit antes de p.StandardOutput.ReadToEnd y el proceso secundario escribe suficiente texto para llenar la secuencia redirigida. El proceso padre esperaría indefinidamente a que salga el proceso hijo. El proceso secundario esperaría indefinidamente a que el padre lea de la secuencia completa de StandardOutput.

Existe un problema similar cuando lee todo el texto tanto de la salida estándar como de las secuencias de error estándar. Por ejemplo, el siguiente código C # realiza una operación de lectura en ambas secuencias.

Convierte el código en esto;

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

Que ahora sale correctamente.

Rico
fuente
2
Un consejo sobre sqlplus: si desea saber si la ejecución del script fue exitosa, puede agregar WHENEVER SQLERROR EXIT SQL.SQLCODE al comienzo del script. De esta forma, el proceso sqlplus devuelve el número de error sql como código de retorno.
devdimi
alguna muestra completa del código fuente completo? ¿Qué es in_database, s ??
Kiquenet
2
Esto no funciona para mí. p.StandardOutput.ReadToEnd();nunca sale
Louis Rhys
2

Hay dos puntos a considerar.

1) Este código fuente funcionó para mí:

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

Configuré el directorio de trabajo en el directorio del script, para que los subguiones dentro del script también funcionen.

Llámalo, por ejemplo, como Execute("usr/pwd@service", "c:\myscripts", "script.sql")

2) Tienes que finalizar tu script SQL con la declaración EXIT;

StefanG
fuente
1

Usando EntityFramework, puede ir con una solución como esta. Yo uso este código para inicializar las pruebas e2e. Para evitar ataques de inyección sql, asegúrese de no generar este script en función de la entrada del usuario o utilizar parámetros de comando para esto (consulte la sobrecarga de ExecuteSqlCommand que acepta parámetros).

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

No pude encontrar ninguna forma exacta y válida de hacer esto. Entonces, después de todo un día, llegué con este código mixto logrado de diferentes fuentes y tratando de hacer el trabajo.

Pero todavía está generando una excepción ExecuteNonQuery: CommandText property has not been Initializeda pesar de que ejecuta con éxito el archivo de script; en mi caso, crea con éxito la base de datos e inserta datos en el primer inicio.

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
fuente
No pude encontrar ninguna forma exacta y válida de hacer esto. Entonces, después de todo un día, llegué con este código mixto logrado de diferentes fuentes y tratando de hacer el trabajo. así que los fusioné todos y obtuve el resultado. Pero sigue generando una excepción "ExecuteNonQuery: la propiedad CommandText no se ha inicializado". Aunque ejecuta con éxito el archivo de secuencia de comandos (en mi caso, crear con éxito la base de datos e insertar datos en el primer inicio).
Muhammad Salman