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.

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

fonte
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();
Hacko
fonte
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;
        }
    }
Xtian11
fonte
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
Binoj Antony
fonte
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();
    }
}
Neelam saini
fonte
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.

O Gato de Botas
fonte
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.

Rico
fonte
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;

StefanG
fonte
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);
            }
        }              
    }
}
martinoss
fonte
-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
        {

        }

    }

}
Muhammad Salman
fonte
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