Выполнить TSQL без использования SMO? - PullRequest
3 голосов
/ 10 ноября 2011

Я поддерживаю простой инструмент БД, который выполняет CreateDatabase в нашей EF модели и использует SMO для запуска некоторых сценариев .sql.

В настоящее время используется:

var svrConnection = new ServerConnection(sqlConnection);
var server = new Server(svrConnection);
server.ConnectionContext.ExecuteNonQuery(fullSqlScript);

Есть ли способ выполнить сценарий TSQL в .Net без использования SMO?

Или, есть ли способ успешно использовать SMO в приложении, не устанавливая его на сервере, на котором я его запускаю?

Любая альтернатива, которая будет мне полезна, не потребует установки на коробке, кроме xcopy сборок для моего инструмента. Он также должен гарантировать, что скрипт будет работать точно так же без дополнительного тестирования / проверки.

Скрипты используют GO и т. Д. И не могут / не должны разбиваться - они генерируются сторонними инструментами (aspnet_regsql.exe) и вручную (но на этом этапе устарели), поэтому я не хочу прикасаться к ним, если я могу избежать этого.

Я почти уверен, что предложения в комментариях к этому ответу неверны, потому что я почти уверен, что GO не может быть напрямую заменен на ;. Если я ошибаюсь, пожалуйста, дайте мне знать:)

Я пытаюсь обойти эту проблему:

Не удалось загрузить файл или сборку 'Microsoft.SqlServer.SqlClrProvider, версия = 10.0.0.0, культура = нейтральная, PublicKeyToken = 89845dcd8080cc91' или одна из ее зависимостей. Система не может найти указанный файл.

Исправление, упомянутое здесь , заключалось в установке SMO, для которой требуются другие пакеты, что для меня не идеально.


Окончательное решение, основанное на Ответ Рассела МакКлюра :

В конце я собираюсь получить файл sqlcmd.exe, поскольку сценарии, написанные для него (все, что содержит GO), могут сделать слишком много для репликации через SqlCommand.ExecuteNonQuery в моем собственном коде. А так как он имеет тот же уровень зависимостей, что и SMO, и потребует некоторой работы для его обертывания с чисто программной точки зрения, я просто собираюсь придерживаться SMO.

Все библиотеки и инструменты, которые я упомянул, являются частью:

http://www.microsoft.com/download/en/details.aspx?id=16978

Ответы [ 4 ]

2 голосов
/ 10 ноября 2011

Вы можете использовать ADO.NET.Таким образом, вам не нужно зависеть от sqlcmd.exe.

Вот очень полезный класс для обработки ваших скриптов (с GO,: setvar, $ (MyVar) и т. Д.).

http://bitmugger.blogspot.com/2008_04_01_archive.html

Я использую слегка модифицированную версию в производстве уже несколько месяцев без проблем.Вот моя версия (не помню, что я изменил - возможно, добавил несколько поддерживаемых команд).

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;

namespace GR.WIX
{
    /// <summary>
    /// Supports running a SQLCmd Mode style statement such as the output from a VS 2008 Database Team Edition Database Project
    /// Only a limited subset of the SQLCmd mode syntax is supported. Other gotchas.
    ///
    ///
    ///
    /// Supported Commands:
    ///     GO (note GO [N] will only be executed once not N times)
    ///     :setvar
    ///     $(MyVar)
    ///     :on error exit
    ///     :on error resume (only for SQL Errors)
    ///     :on error ignore (only for SQL Errors)
    ///
    /// The Following SQLCMD Commands are recognized but ignored. They will not crash your script if encountered but will be skipped.
    ///     :ED
    ///     :Error
    ///     :!!
    ///     :Perftrace
    ///     :Quit
    ///     :Exit
    ///     :Help
    ///     :XML
    ///     :r
    ///     :ServerList
    ///     :Listvar
    ///
    /// The following SQLCMD pre-defined variables are pre-defined by this class just like they are by SQLCMD
    /// The only difference is SQLCMD actually used and/or updated these variable. This class simply has them predefined
    /// with much the same values as SQLCMD did. The class allows you to change ALL variables (unlike SQLCMD) where some are
    /// read only.
    ///     SQLCMDUSER ""
    ///     SQLCMDPASSWORD
    ///     SQLCMDSERVER {Server Name}
    ///     SQLCMDWORKSTATION {Computer Name}
    ///     SQLCMDLOGINTIMEOUT {Connection Timeout}
    ///     SQLCMDDBNAME {Database Name}
    ///     SQLCMDHEADERS "0"
    ///     SQLCMDCOLSEP " "
    ///     SQLCMDCOLWIDTH "0"
    ///     SQLCMDPACKETSIZE "4096"
    ///     SQLCMDERRORLEVEL "0"
    ///     SQLCMDMAXVARTYPEWIDTH "256"
    ///     SQLCMDMAXFIXEDTYPEWIDTH "0"
    ///     SQLCMDEDITOR "edit.com"
    ///     SQLCMDINI ""
    ///
    /// The following pre-defnined variables ARE used by the class and their values when set are not ignored
    ///     SQLCMDSTATTIMEOUT "0"
    ///   
    /// One Additional Variable is defined so that scripts could potentially detect they are running in this class instead
    /// of SQLCmd.
    ///     SQLCMDREAL "0"
    /// </summary>
    public class ExecuteSqlCmdMode
    {
        #region Fields
        private readonly Dictionary<string, string> variables;
        private readonly List<string> lockedVariables;
        private ErrorMode errorMode;
        private readonly SqlConnection connection;
        private readonly List<string> ignoredCommands;
        private bool allowVariableOverwrites;
        #endregion Fields

        #region Properties
        /// <summary>
        /// Gets or sets a value indicating whether to allow variable overwrites.
        /// If True then even though a variable is specified externally it may be overwritten by :SetVar in the script. If False then the reverse
        /// variables specified externally superscede :setvar.
        /// Default = false
        /// </summary>
        /// <value>true if allow variable overwrites; otherwise, false.</value>
        public bool AllowVariableOverwrites
        {
            get { return allowVariableOverwrites; }
            set { allowVariableOverwrites = value; }
        }
        #endregion Properties

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="ExecuteSqlCmdMode"/> class.
        /// </summary>
        /// <param name="sqlConnection">The SQL conn.</param>
        public ExecuteSqlCmdMode(SqlConnection sqlConnection)
        {
            // Check for legal values
            if (sqlConnection == null)
            {
                throw new Exception("connection cannot be null");
            }

            // Set connection variable from supplied SQLConnection.
            connection = sqlConnection;

            // Load up the script variables.
            variables = new Dictionary<string, string>();
            variables.Add("SQLCMDUSER", "");
            variables.Add("SQLCMDPASSWORD", "");
            variables.Add("SQLCMDSERVER", sqlConnection.DataSource);
            variables.Add("SQLCMDWORKSTATION", sqlConnection.WorkstationId);
            variables.Add("SQLCMDDBNAME", sqlConnection.Database);
            variables.Add("SQLCMDLOGINTIMEOUT", sqlConnection.ConnectionTimeout.ToString());
            variables.Add("SQLCMDSTATTIMEOUT", "0");
            variables.Add("SQLCMDHEADERS", "0");
            variables.Add("SQLCMDCOLSEP", "");
            variables.Add("SQLCMDCOLWIDTH", "0");
            variables.Add("SQLCMDPACKETSIZE", "4096");
            variables.Add("SQLCMDERRORLEVEL", "0");
            variables.Add("SQLCMDMAXVARTYPEWIDTH", "256");
            variables.Add("SQLCMDMAXFIXEDTYPEWIDTH", "0");
            variables.Add("SQLCMDEDITOR", "edit.com");
            variables.Add("SQLCMDINI", "");
            variables.Add("SQLCMDREAL", "0");

            // Setup pre-locked variables.
            lockedVariables = new List<string>();
            lockedVariables.Add("SQLCMDREAL");

            // Setup the list of commands to be ignored.
            ignoredCommands = new List<string>();
            ignoredCommands.Add(":ED");
            ignoredCommands.Add(":ERROR");
            ignoredCommands.Add(":!!");
            ignoredCommands.Add(":PERFTRACE");
            ignoredCommands.Add(":QUIT");
            ignoredCommands.Add(":EXIT");
            ignoredCommands.Add(":HELP");
            ignoredCommands.Add(":XML");
            //ignoredCommands.Add(":R");
            ignoredCommands.Add(":SERVERLIST");
            ignoredCommands.Add(":LISTVAR");

            // Some other misc values.
            errorMode = ErrorMode.ErrExit;
            allowVariableOverwrites = false;
        }

        #endregion Constructor

        /// <summary>
        /// Sets a variable in advance of script execution.
        /// </summary>
        /// <param name="variableName">Name of the variable.</param>
        /// <param name="variableValue">The variable value.</param>
        public void SetVariable(string variableName, string variableValue)
        {
            variableName = variableName.Trim().ToUpper();
            if (variableName.Length == 0  || variableName.Contains(" "))
            {
                throw new Exception(string.Format("Variable name {0} cannot be blank or contain spaces", variableName));
            }

            // See if we already have this variable
            if (variables.ContainsKey(variableName))
            {
                variables[variableName] = variableValue;
            }
            else
            {
                variables.Add(variableName, variableValue);

                if (!allowVariableOverwrites)
                {
                    lockedVariables.Add(variableName);
                }
            }
        }

        /// <summary>
        /// Executes the specified SQL script.
        /// </summary>
        /// <param name="scriptToExecute">The SQL script to execute.</param>
        public List<Exception> Execute(string scriptToExecute)
        {
            var exceptions = new List<Exception>();
            var queryBlock = new StringBuilder();

            connection.Open();


            var scriptLines = (scriptToExecute.Replace(Environment.NewLine, "\n") + "\nGO\n").Split('\n');

            // Loop each line in the script
            for (var i = 0; i < scriptLines.GetUpperBound(0); i++)
            {
                // Prepare a specially modified version of the line for checking for commands.
                var ucaseLine = scriptLines[i].Replace("\t", " ").Trim().ToUpper() + " ";

                // See if it's one of the commands to be ignored.
                if (ignoredCommands.Contains(ucaseLine.Split(' ')[0]))
                {
                    // Just ignore this line.
                }
                else if (ucaseLine.StartsWith("GO "))
                {
                    // We have a GO line (everything after GO on the line is ignored). Execute the block
                    // we have gathered so far.
                    ExecuteBlock(queryBlock, exceptions);
                    // After a GO command, we reset our query.
                    queryBlock = new StringBuilder();
                }
                else if (ucaseLine.StartsWith(":SETVAR "))
                {
                    // We have found a SetVar line. Add (or update) the variable and its value to our list.
                    SetVariableValue(scriptLines[i]);
                }
                else if (ucaseLine.StartsWith(":ON ERROR "))
                {
                    // Handle :on error.
                    HandleOnErrorCommand(i, ucaseLine);
                }
                else if (ucaseLine.StartsWith(":R "))
                {
                    // TODO: Handle this case.
                }
                else
                {
                    // Regular SQL Line to have variables replaced on then added to SQLCmd for execution.

                    // Replace variables with its value for the line (if any).
                    var noVariableVersion = ReplaceVariablesWithValue(scriptLines[i]);

                    // Add it to the current block of code to execute.
                    queryBlock.AppendLine(noVariableVersion);
                }
            }
            return exceptions;
        }

        private string ReplaceVariablesWithValue(string temp)
        {
            if (temp.Length > 4 && temp.Contains("$("))
            {
                // Loop each variable to check the line for.
                foreach (var keyPair in variables)
                {
                    var searchFor = string.Format("$({0})", keyPair.Key);
                    var begPos = temp.ToUpper().IndexOf(searchFor);
                    while (begPos >= 0)
                    {
                        // Make the variable substitution
                        var endPos = begPos + searchFor.Length;
                        temp = temp.Substring(0, begPos) + keyPair.Value + temp.Substring(endPos, temp.Length - endPos);

                        // Calculate a new begPos
                        begPos = temp.ToUpper().IndexOf(string.Format(searchFor));
                    }
                }
            }
            return temp;
        }

        private void ExecuteBlock(StringBuilder sqlCommand, List<Exception> exceptions)
        {
            try
            {
                if (sqlCommand.Length > 0)
                {
                    // Attempt the SQL command.
                    using (var sqlComm = new SqlCommand(sqlCommand.ToString(), connection))
                    {
                        sqlComm.CommandTimeout = 120;
                        sqlComm.ExecuteNonQuery();
                    }
                }
            }
            catch (Exception ex)
            {
                if (errorMode != ErrorMode.ErrIgnore)
                {
                    throw new Exception("Error executing " + sqlCommand, ex);
                }

                exceptions.Add(new Exception("Error executing " + sqlCommand, ex));
            }
        }
        private void HandleOnErrorCommand(int i, string ucaseLine)
        {
            var temp = ucaseLine.Substring(10, ucaseLine.Length - 10).Trim();
            if (temp == "EXIT")
            {
                errorMode = ErrorMode.ErrExit;
            }
            else if (temp == "RESUME" || temp == "IGNORE")
            {
                errorMode = ErrorMode.ErrIgnore;
            }
            else
            {
                throw new Exception(string.Format("Unknown On Error mode '{0}' on line {1}", temp, i));
            }
        }

        private void SetVariableValue(string scriptLine)
        {
            var temp = scriptLine.Trim().Substring(8, scriptLine.Trim().Length - 8);
            var begPos = temp.IndexOf(" ");

            var varName = temp.Substring(0, begPos).Trim().ToUpper();
            var varValue = temp.Substring(begPos + 1, temp.Length - begPos - 1).Trim();
            if (varValue.StartsWith("\"") && varValue.EndsWith("\""))
            {
                varValue = varValue.Substring(1, varValue.Length - 2);
            }
            else
            {
                throw new Exception(string.Format("Improperly formatted :SetVar on the following line {0}.", scriptLine));
            }

            if (variables.ContainsKey(varName))
            {
                if (!lockedVariables.Contains(varName))
                {
                    variables[varName] = varValue;
                }
            }
            else
            {
                variables.Add(varName, varValue);
            }
        }
    }

    /// <summary>
    /// Legal values for the error mode
    /// Error mode controls what happens when a SQL Error occurs
    /// </summary>
    public enum ErrorMode
    {
        ErrExit,
        ErrIgnore
    }
}

Используйте это так:

/// <summary>
        /// Executes the SQL script.
        /// </summary>
        /// <param name="serverName">Name of the server.</param>
        /// <param name="scriptPath">The path of the script to execute.</param>
        /// <param name="variables">The variables.</param>
        /// <returns></returns>
        private static void ExecuteSqlScript(string serverName, string scriptPath, Dictionary<string, string> variables)
        {
            using (var connection = new SqlConnection(string.Format(SqlConnectionFormat, serverName)))
            {
                var mode = new ExecuteSqlCmdMode(connection);

                // Add variables.
                foreach (var variable in variables)
                {
                    mode.SetVariable(variable.Key, variable.Value);
                }

                mode.Execute(FileToString(scriptPath));
            }
        }
2 голосов
/ 10 ноября 2011

Запустите T-SQL через sqlcmd.exe .

Или то, что я обычно делаю, на самом деле читает содержимое файла (мои sql-файлы обычно являются встроенными ресурсами), а затем передает его этой функции, чтобы разбить его на пакеты, приемлемые для ADO.NET:

.
public static string[] ParseSqlStatementBatch(string sqlStatementBatch)
{
   // split the sql into seperate batches by dividing on the GO statement
   Regex sqlStatementBatchSplitter = new Regex(@"^\s*GO\s*\r?$", RegexOptions.Multiline | RegexOptions.IgnoreCase);
   return sqlStatementBatchSplitter.Split(sqlStatementBatch);
}

Вот пример использования:

string[] sqlStatements = DataAccess.DatabaseWrapper.ParseSqlStatementBatch(FileContents);

using (SqlConnection sqlConnection = new SqlConnection(connectionString))
{
   sqlConnection.Open();
   using (SqlCommand command = sqlConnection.CreateCommand())
   {
      command.CommandType = CommandType.Text;
      command.CommandTimeout = Registry.RegistryWrapper.GetSqlCommandTimeout();
      foreach (string sqlStatement in sqlStatements)
      {
         if (sqlStatement.Length > 0)
         {
            command.CommandText = sqlStatement;
            command.ExecuteNonQuery();
1 голос
/ 25 июля 2013

Мое решение этой проблемы:

Не удалось загрузить файл или сборку 'Microsoft.SqlServer.SqlClrProvider, Версия = 10.0.0.0, Культура = нейтральная, PublicKeyToken = 89845dcd8080cc91' или одна из ее зависимостей,Системе не удается найти указанный файл

С выходом FW 4.0 MS введен новый тип dynamic .Я создал класс, который загружает SMO DLL во время выполнения, используя этот тип.Пример:

public dynamic Load(string assemblyName, string assemblyNamespace, bool instantiate, params     object[] constructorParams)
{
    dynamic dynamicAssembly = null;
    try
    {
       string assemblyPath = GetAssemblyPath(assemblyName);
       Assembly assembly = Assembly.LoadFrom(assemblyPath);
       if (instantiate)
       {
           Type assemblyType = assembly.GetType(assemblyNamespace);
           if (constructorParams == null)
              dynamicAssembly = Activator.CreateInstance(assemblyType);
           else
              dynamicAssembly = Activator.CreateInstance(assemblyType, constructorParams);
       }
    }
    catch (Exception exc)
    {
       throw;
    }
    return dynamicAssembly;
}

Когда вы хотите инициализировать объект SMO, вы должны знать все пространство имен, имя DLL, где реализуется объект, и параметры конструктора.О методе GetAssemblyPath проверьте это решение .

Пример:

 var server = Load("Microsoft.SqlServer.Smo", "Microsoft.SqlServer.Management.Smo.Server", DataSource.ServerName);

Существует множество плюсов и минусов в использовании dynamic.Более подробную информацию вы можете узнать на сайте MS http://msdn.microsoft.com/en-us/library/vstudio/dd264741.aspx

С этим решением вам не нужно беспокоиться о том, какой SQL-сервер установлен на клиентском компьютере, потому что всегда загружается последняя версия из GAC.

Основным недостатком представления о программировании является то, что компилятор не может знать, какой тип используется для динамического (время компиляции), и если вы не будете достаточно осторожны, вы можете быстро вызвать (время выполнения) ошибки, которые трудно поддерживать.

О других ваших вопросах, на которые уже отвечали другие.

С уважением,

1 голос
/ 10 ноября 2011

Вот код, который может помочь. Я использую это для системы сборки / развертывания.

public void ExecuteScript(string scriptPath, SqlTransaction trans)
{
    var batch = new StringBuilder();
    var script = File.ReadAllLines(scriptPath);

    for (int i = 0; i < script.Length; i++)
    {
        if (script[i].Trim().StartsWith("GO", StringComparison.CurrentCultureIgnoreCase))
        {
            // If a line starts with a GO it means that a batch should be executed (Sql Server doesn't understand GO).
            ExecuteBatch(batch.ToString(), trans);
            batch = new StringBuilder();
        }
        else
            batch.AppendLine(script[i]);
    }
    // make sure we execute the last batch (it might not end with GO).
    ExecuteBatch(batch.ToString(), trans);        
}

private static void ExecuteBatch(string batch, SqlTransaction trans)
{
    batch = batch.Trim();
    if (batch == "") return;
    var cmd = new SqlCommand(batch, trans.Connection, trans);
    cmd.CommandTimeout = 0;
    cmd.ExecuteNonQuery();
}

ПРИМЕЧАНИЕ. Это не совсем оригинальный код (у меня там были некоторые дополнительные вещи, которые вам, вероятно, не нужны), и я не проверял свои изменения выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...