Як виконати файл сценарію .SQL за допомогою c #


140

Я впевнений, що на це питання вже відповіли, проте мені не вдалося знайти відповідь за допомогою інструменту пошуку.

Використовуючи c # Я хотів би запустити .sql файл. Файл sql містить кілька операторів sql, деякі з яких розбиті на кілька рядків. Я спробував прочитати файл і спробував виконати файл за допомогою ODP.NET ... однак я не думаю, що ExecuteNonQuery дійсно призначений для цього.

Тому я спробував використовувати sqlplus через нерестуючий процес ... однак, якщо я не породив цей процес за допомогою UseShellExecute, встановленого на true, sqlplus не зависне і ніколи не вийде. Ось код, НЕ РОБОТИ.

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 ніколи не повертається .... Якщо я не встановив UseShellExecute на true. Побічним ефектом UseShellExecute є те, що ви не можете зафіксувати перенаправлений вихід.


8
Привіт, містере Річ, ваше питання стосувалося Oracle, і ви прийняли рішення, яке стосується sql-сервера? Ви змінили DB на сервер sql?
Акшай J

Відповіді:


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
Чудово! Це рішення працювало для мене, щоб я міг скидати та відтворювати базу даних, а також додавати таблиці (через посилання на файл сценарію SQL).
Псалом Огре3333

11
Цей метод не дозволяє використовувати у вашому скрипті команду "GO", яка дозволена під час запуску сценарію з SQL Management Studio або команди osql. msdn.microsoft.com/en-us/library/ms188037.aspx
Rn222

20
Rn222: Я думаю, що ви переплутали методи ExecuteNonQuery, SqlCommand.ExecuteNonQuery не дозволить використовувати команди "GO", однак Server.ConnectionContext.ExecuteNonQuery, безумовно, робить (я зараз його використовую).
ПітерБелм

44
Зауважте, що вам потрібно додати посилання на проект, до Microsoft.SqlServer.ConnectionInfo, Microsoft.SqlServer.Management.Sdk та Microsoft.SqlServer.Smo, щоб ця відповідь спрацювала.
thomasb

8
Для мене це не робота при використанні .net 4.0 / 4.5, при посиланнях на 110 \ SDK \ Складання Рішення , яке я знайшов змінював App.config до<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup>
Абір

107

Я спробував це рішення з Microsoft.SqlServer.Management, але він не працював добре з .NET 4.0, тому я написав інше рішення, використовуючи лише .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();

Саме так. Це рішення навіть не закриє файл після його використання. Це може бути критично важливим.
Mathias Lykkegaard Lorenzen

1
Використовуйте "RegexOptions.Multiline | RegexOptions.IgnoreCase", щоб відповідати і випадкам "Go" або "go".
Анкуш

1
Я думаю, що також слід використовувати прапор RegexOptions.CultureInvariant.
Дейв Андерсен

3
Це не працює на 100%: 'GO' може приймати числовий параметр.
неросло

16

Це працює на Framework 4.0 або вище. Підтримує "GO". Також покажіть повідомлення про помилку, рядок та команду 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
Хороший код, один дуже незначна річ в тому , що не потрібно connection.Close()з'єднання буде закрито usingви обвив його.
Дружна

Чудова робота. Для мене це працювало «прямо з коробки».
Stephen85

8

Покладіть команду для виконання скрипту sql у пакетний файл, а потім запустіть наведений нижче код

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 )

у пакетний файл напишіть щось подібне (зразок для sql-сервера)

osql -E -i %1

6

Це працює для мене:

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

Додано додаткові вдосконалення для відповіді на сураїти:

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

Крім того, мені довелося додати наступні посилання на мій проект:

  • 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

Я не маю уявлення, чи правильно це використовувати dll: s, оскільки в C: \ Program Files \ Microsoft SQL Server є кілька папок, але в моїй програмі ці дві роботи.


Це працювало для мене .Net 4.7. Мені не потрібні були інші згадані сураїтом дрилі. Однак мені довелося використовувати версію 13.0.0.0 як для Microsoft.SqlServer.ConnectionInfo, так і для Microsoft.SqlServer.Smo, оскільки 13.100.0.0 викинув винятки під час ідентифікації ServerConnection.
Кевін Фіхтер

4

Я встиг опрацювати відповідь, прочитавши посібник :)

Цей витяг з MSDN

Приклад коду дозволяє уникнути умови глухого кута, викликаючи p.StandardOutput.ReadToEnd перед p.WaitForExit. Умова тупикового зв'язку може призвести, якщо батьківський процес викликає p.WaitForExit перед p.StandardOutput.ReadToEnd і дочірній процес записує достатньо тексту для заповнення переспрямованого потоку. Батьківський процес буде чекати нескінченно часу, щоб дочірній процес вийшов. Дочірній процес нескінченно буде чекати, коли батько прочитає з повного потоку StandardOutput.

Аналогічна проблема виникає, коли ви читаєте весь текст із стандартних потоків помилок та стандартних помилок. Наприклад, наступний код C # виконує операцію зчитування в обох потоках.

Перетворює код у це;

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

Який зараз виходить правильно.


2
Порада щодо sqlplus: якщо ви хочете знати, чи вдале виконання сценарію, ви можете додати WHENEVER SQLERROR EXIT SQL.SQLCODE на початку сценарію. Таким чином процес sqlplus повертає номер помилки sql як код повернення.
devdimi

будь-який повний зразок вихідного коду? що таке in_database, s ??
Кікенет

2
це не працює для мене. p.StandardOutput.ReadToEnd();ніколи не виходить
Louis Rhys

2

Є два моменти, які слід враховувати.

1) Цей вихідний код працював для мене:

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

Я встановлюю робочий каталог в каталог скриптів, щоб підкрипти в рамках сценарію також працювали.

Назвіть це, наприклад, як Execute("usr/pwd@service", "c:\myscripts", "script.sql")

2) Ви повинні доопрацювати свій SQL-скрипт із заявою EXIT;


1

Використовуючи EntityFramework, ви можете вирішити подібне рішення. Я використовую цей код для ініціалізації тестів e2e. Щоб запобігти атакам введенням sql, переконайтеся, що не створювати цей скрипт на основі введення користувача або використовувати для цього параметри команд (див. Перевантаження ExecuteSqlCommand, що приймає параметри).

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

Я не зміг знайти точного і достовірного способу зробити це. Тож через цілий день я прийшов із цим змішаним кодом, досягнутим з різних джерел, і намагався виконати роботу.

Але він все ще генерує виняток, ExecuteNonQuery: CommandText property has not been Initializedнавіть незважаючи на те, що він успішно запускає файл сценарію - у моєму випадку він успішно створює базу даних та вставляє дані під час першого запуску.

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
        {

        }

    }

}

Я не зміг знайти точного і достовірного способу зробити це. Тож через цілий день я прийшов із цим змішаним кодом, досягнутим з різних джерел, і намагався виконати роботу. тому я з’єднав їх усіх і дав результат. Але він все ще генерує виняток "ExecuteNonQuery: Властивість CommandText не була ініціалізована." Хоча він успішно запускає файл сценарію (У моєму випадку успішно створити базу даних та вставити дані під час першого запуску).
Мухаммад Салман
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.