Como executar um arquivo de script .SQL usando c #


140

Tenho certeza de que esta pergunta já foi respondida, mas não consegui encontrar uma resposta usando a ferramenta de pesquisa.

Usando c #, eu gostaria de executar um arquivo .sql. O arquivo sql contém várias instruções sql, algumas das quais são divididas em várias linhas. Eu tentei ler o arquivo e tentei executá-lo usando o ODP.NET ... no entanto, não acho que o ExecuteNonQuery seja realmente projetado para fazer isso.

Então, tentei usar o sqlplus via gerar um processo ... no entanto, a menos que eu gerasse o processo com UseShellExecute definido como true, o sqlplus seria interrompido e nunca sairia. Aqui está o código que NÃO 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 retorna .... A menos que eu defina UseShellExecute como true. Um efeito colateral do UseShellExecute é que você não pode capturar a saída redirecionada.


8
Olá Sr. Rich, sua pergunta era sobre Oracle e você aceitou uma solução que era para servidor sql? Você mudou seu banco de dados para sql server?
Akshay J

Respostas:


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
Ótimo! Essa solução funcionou para mim por poder eliminar e recriar um banco de dados e adicionar tabelas (por meio do arquivo de script SQL referenciado).
Ogre Psalm33

11
Este método não permite o uso do comando "GO" em seu script, o que é permitido quando você executa um script no SQL Management Studio ou no comando osql. msdn.microsoft.com/en-us/library/ms188037.aspx
Rn222 7/11/11

20
Rn222: Acho que você confundiu os métodos ExecuteNonQuery, SqlCommand.ExecuteNonQuery não permitirá o uso de comandos "GO", no entanto Server.ConnectionContext.ExecuteNonQuery definitivamente o faz (estou usando agora).
22412 PeterBelm

44
Observe que você precisa adicionar referências ao projeto para Microsoft.SqlServer.ConnectionInfo, Microsoft.SqlServer.Management.Sdk e Microsoft.SqlServer.Smo para que esta resposta funcione.
Thomasb

8
Para mim, não funcionou ao usar .net 4.0 / 4.5, ao fazer referência a 110 \ SDK \ Assemblies A solução que encontrei foi alterar o app.Config para<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup>
Abir

107

Tentei esta solução com Microsoft.SqlServer.Management, mas não funcionou bem com o .NET 4.0, por isso escrevi outra solução usando apenas a estrutura de bibliotecas do .NET.

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

Exatamente. Essa solução nem fecha o arquivo depois de terminar de usá-lo. Isso pode ser crítico.
Mathias Lykkegaard Lorenzen

1
Use "RegexOptions.Multiline | RegexOptions.IgnoreCase" para corresponder também aos casos "Go" ou "go".
Ankush

1
Acho que o sinalizador RegexOptions.CultureInvariant também deve ser usado.
Dave Andersen

3
Isso não está 100% funcionando: 'GO' pode aceitar parâmetro numérico.
nothrow

16

Isso funciona no Framework 4.0 ou superior. Suporta "GO". Mostrar também a mensagem de erro, linha e 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
Código legal, uma coisa muito pequena é que não precisa que connection.Close()a conexão seja fechada quando usingvocê a envolve. #
056

Ótimo trabalho. Funcionou 'direto da caixa' para mim.
Stephen85 15/01

8

Coloque o comando para executar o script sql em um arquivo em lotes e execute o código abaixo

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 )

no arquivo em lote, escreva algo parecido com isto (exemplo para sql server)

osql -E -i %1

6

Isso funciona para mim:

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

Adicionadas melhorias adicionais à resposta dos 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);
            }
        }
    }
}

Além disso, eu tive que adicionar as seguintes referências ao meu projeto:

  • 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

Não tenho idéia se essas são as dll: s certas para usar, pois existem várias pastas em C: \ Arquivos de Programas \ Microsoft SQL Server, mas no meu aplicativo essas duas funcionam.


Isso funcionou para mim no .Net 4.7. Eu não precisava das outras dlls mencionadas por surajit. No entanto, tive que usar a versão 13.0.0.0 para Microsoft.SqlServer.ConnectionInfo e Microsoft.SqlServer.Smo, pois 13.100.0.0 lançou exceções ao instanciar o ServerConnection.
Kevin Fichter

4

Eu consegui descobrir a resposta lendo o manual :)

Este extrato do MSDN

O exemplo de código evita uma condição de conflito chamando p.StandardOutput.ReadToEnd antes de p.WaitForExit. Uma condição de conflito pode resultar se o processo pai chamar p.WaitForExit antes de p.StandardOutput.ReadToEnd e o processo filho gravar texto suficiente para preencher o fluxo redirecionado. O processo pai esperaria indefinidamente o processo filho sair. O processo filho aguardaria indefinidamente que o pai lesse o fluxo padrão do StandardOutput.

Há um problema semelhante quando você lê todo o texto da saída padrão e dos fluxos de erro padrão. Por exemplo, o código C # a seguir executa uma operação de leitura nos dois fluxos.

Transforma o código nisso;

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 agora sai corretamente.


2
Uma dica sobre o sqlplus: se você quiser saber se a execução do script foi bem-sucedida, adicione WHENEVER SQLERROR EXIT SQL.SQLCODE no início do script. Dessa forma, o processo sqlplus retorna o número do erro sql como código de retorno.
Devdimi 23/03/09

algum exemplo completo de código fonte? o que é in_database, s ??
Kiquenet

2
isso não funciona para mim. p.StandardOutput.ReadToEnd();nunca sai
Louis Rhys

2

Há dois pontos a considerar.

1) Este código fonte funcionou para mim:

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

Defino o diretório de trabalho como o diretório de scripts, para que os sub-scripts dentro do script também funcionem.

Chame, por exemplo, como Execute("usr/pwd@service", "c:\myscripts", "script.sql")

2) Você deve finalizar seu script SQL com a instrução EXIT;


1

Usando o EntityFramework, você pode usar uma solução como esta. Eu uso esse código para inicializar os testes e2e. Para evitar ataques de injeção de sql, certifique-se de não gerar esse script com base na entrada do usuário ou use parâmetros de comando para isso (consulte a sobrecarga de ExecuteSqlCommand que aceita 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);
            }
        }              
    }
}

-1

Não consegui encontrar nenhuma maneira exata e válida de fazer isso. Então, depois de um dia inteiro, eu vim com esse código misto obtido de diferentes fontes e tentando fazer o trabalho.

Mas ainda está gerando uma exceção ExecuteNonQuery: CommandText property has not been Initialized, apesar de executar com êxito o arquivo de script - no meu caso, ele cria o banco de dados e insere dados na primeira inicialização.

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
        {

        }

    }

}

Não consegui encontrar nenhuma maneira exata e válida de fazer isso. Então, depois de um dia inteiro, eu vim com esse código misto obtido de diferentes fontes e tentando fazer o trabalho. então eu juntei todos eles e fiz o resultado. Mas ainda está gerando uma exceção "A propriedade ExecuteNonQuery: CommandText não foi inicializada". Embora ele execute com êxito o arquivo de script (no meu caso, crie com sucesso o banco de dados e insira os dados na primeira inicialização).
Muhammad Salman
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.