Comment exécuter un fichier de script .SQL en utilisant c #

140

Je suis sûr que cette question a déjà été répondue, mais je n'ai pas pu trouver de réponse à l'aide de l'outil de recherche.

En utilisant c #, j'aimerais exécuter un fichier .sql. Le fichier sql contient plusieurs instructions sql, dont certaines sont réparties sur plusieurs lignes. J'ai essayé de lire le fichier et j'ai essayé d'exécuter le fichier en utilisant ODP.NET ... mais je ne pense pas qu'ExecuteNonQuery soit vraiment conçu pour faire cela.

J'ai donc essayé d'utiliser sqlplus via la création d'un processus ... Cependant, à moins que je ne lance le processus avec UseShellExecute défini sur true, sqlplus se bloquerait et ne quitterait jamais. Voici le code qui NE FONCTIONNE PAS.

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 ne retourne jamais .... Sauf si j'ai défini UseShellExecute sur true. Un effet secondaire de UseShellExecute est que vous ne pouvez pas capturer la sortie redirigée.

Riches
la source
8
Bonjour M. Rich, votre question portait sur Oracle et vous avez accepté une solution qui était pour le serveur SQL? Vous avez changé votre base de données en serveur SQL?
Akshay J

Réponses:

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

la source
4
Génial! Cette solution a fonctionné pour moi pour pouvoir supprimer et recréer une base de données et ajouter des tables (via le fichier de script SQL référencé).
Ogre Psalm33
11
Cette méthode ne permet pas d'utiliser la commande "GO" dans votre script qui est autorisée lorsque vous exécutez un script depuis SQL Management Studio ou la commande osql. msdn.microsoft.com/en-us/library/ms188037.aspx
Rn222
20
Rn222: Je pense que vous avez confondu les méthodes ExecuteNonQuery, SqlCommand.ExecuteNonQuery n'autorise pas l'utilisation des commandes "GO", mais Server.ConnectionContext.ExecuteNonQuery le fait définitivement (je l'utilise en ce moment).
PeterBelm
44
Notez que vous devez ajouter des références au projet, à Microsoft.SqlServer.ConnectionInfo, Microsoft.SqlServer.Management.Sdk et Microsoft.SqlServer.Smo pour que cette réponse fonctionne.
thomasb
8
Pour moi, cela n'a pas fonctionné lors de l'utilisation de .net 4.0 / 4.5, lors du référencement 110 \ SDK \ Assemblies La solution que j'ai trouvée était de changer l'application.Config en<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup>
Abir
107

J'ai essayé cette solution avec Microsoft.SqlServer.Management mais cela ne fonctionnait pas bien avec .NET 4.0, j'ai donc écrit une autre solution en utilisant uniquement le 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
la source
Exactement. Cette solution ne fermera même pas le fichier une fois son utilisation terminée. Cela pourrait être critique.
Mathias Lykkegaard Lorenzen
1
Utilisez «RegexOptions.Multiline | RegexOptions.IgnoreCase» pour faire correspondre les cas «Go» ou «Go» également.
Ankush
1
Je pense que l'indicateur RegexOptions.CultureInvariant devrait également être utilisé.
Dave Andersen
3
Cela ne fonctionne pas à 100%: «GO» peut accepter un paramètre numérique.
nothrow le
16

Cela fonctionne sur Framework 4.0 ou supérieur. Prend en charge "GO". Afficher également le message d'erreur, la ligne et la commande 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
la source
3
Bon code, une chose très mineure est qu'il n'a pas besoin de connection.Close()la connexion sera fermée par le usingvous l'avez enveloppé.
Amicable
Bon travail. Cela a fonctionné «tout droit sorti de la boîte» pour moi.
Stephen85
8

Mettez la commande pour exécuter le script sql dans un fichier batch, puis exécutez le code ci-dessous

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 )

dans le fichier batch, écrivez quelque chose comme ceci (exemple pour le serveur sql)

osql -E -i %1
Binoj Antony
la source
6

Cela fonctionne pour moi:

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
la source
4

Ajout d'améliorations supplémentaires à la réponse 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);
            }
        }
    }
}

De plus, j'ai dû ajouter les références suivantes à mon projet:

  • 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

Je n'ai aucune idée si ce sont les bonnes dll: s à utiliser car il y a plusieurs dossiers dans C: \ Program Files \ Microsoft SQL Server mais dans mon application, ces deux fonctionnent.

Chat Botté
la source
Cela a fonctionné pour moi dans .Net 4.7. Je n'avais pas besoin des autres DLL mentionnées par surajit. Cependant, j'ai dû utiliser la version 13.0.0.0 pour Microsoft.SqlServer.ConnectionInfo et Microsoft.SqlServer.Smo, car 13.100.0.0 a levé des exceptions lors de l'instanciation de ServerConnection.
Kevin Fichter le
4

J'ai réussi à trouver la réponse en lisant le manuel :)

Cet extrait du MSDN

L'exemple de code évite une condition de blocage en appelant p.StandardOutput.ReadToEnd avant p.WaitForExit. Une condition de blocage peut se produire si le processus parent appelle p.WaitForExit avant p.StandardOutput.ReadToEnd et que le processus enfant écrit suffisamment de texte pour remplir le flux redirigé. Le processus parent attendrait indéfiniment la fin du processus enfant. Le processus enfant attendrait indéfiniment que le parent lise à partir du flux StandardOutput complet.

Il existe un problème similaire lorsque vous lisez tout le texte des flux de sortie standard et d'erreur standard. Par exemple, le code C # suivant effectue une opération de lecture sur les deux flux.

Transforme le code en ceci;

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

Qui sort maintenant correctement.

Riches
la source
2
Un conseil concernant sqlplus: si vous voulez savoir si l'exécution du script a réussi, vous pouvez ajouter WHENEVER SQLERROR EXIT SQL.SQLCODE au début du script. De cette façon, le processus sqlplus renvoie le numéro d'erreur sql comme code de retour.
devdimi
un échantillon complet de code source? qu'est-ce que in_database, s ??
Kiquenet le
2
cela ne fonctionne pas pour moi. p.StandardOutput.ReadToEnd();ne sort jamais
Louis Rhys
2

Il y a deux points à considérer.

1) Ce code source a fonctionné pour moi:

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

J'ai défini le répertoire de travail sur le répertoire de script, de sorte que les sous-scripts du script fonctionnent également.

Appelez-le par exemple comme Execute("usr/pwd@service", "c:\myscripts", "script.sql")

2) Vous devez finaliser votre script SQL avec l'instruction EXIT;

StefanG
la source
1

En utilisant EntityFramework, vous pouvez opter pour une solution comme celle-ci. J'utilise ce code pour initialiser les tests e2e. Pour éviter les attaques par injection SQL, assurez-vous de ne pas générer ce script en fonction de l'entrée utilisateur ou d'utiliser des paramètres de commande pour cela (voir surcharge d'ExecuteSqlCommand qui accepte les paramètres).

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
la source
-1

Je n'ai trouvé aucun moyen exact et valide de faire cela. Donc, après une journée entière, je suis venu avec ce code mixte réalisé à partir de différentes sources et en essayant de faire le travail.

Mais il génère toujours une exception ExecuteNonQuery: CommandText property has not been Initializedmême s'il exécute avec succès le fichier de script - dans mon cas, il crée avec succès la base de données et insère des données au premier démarrage.

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
la source
Je n'ai trouvé aucun moyen exact et valable de faire cela. Donc, après une journée entière, je suis venu avec ce code mixte réalisé à partir de différentes sources et en essayant de faire le travail. alors je les ai tous fusionnés et j'ai obtenu le résultat. Mais il génère toujours une exception «ExecuteNonQuery: la propriété CommandText n'a pas été initialisée». Bien qu'il exécute avec succès le fichier de script (dans mon cas, créez avec succès la base de données et insérez les données au premier démarrage).
Muhammad Salman