如何通过C#代码部署存储过程?

时间:2019-12-23 11:08:01

标签: c# sql .net sql-server stored-procedures

我有一个类似的存储过程

IF (OBJECT_ID('sp_InsertDevice', 'P') IS NOT NULL) 
    DROP PROCEDURE sp_InsertDevice 
GO

CREATE PROCEDURE sp_InsertDevice
    @serialNumber NVARCHAR(8),
    @modelName NVARCHAR(40),
    @userId INT
AS
BEGIN
    INSERT INTO Device (SerialNumber, ModelName, UserID)
    VALUES (@serialNumber, @modelName, @userId)

    SELECT CAST(SCOPE_IDENTITY() AS INT);
END

和用于部署它的C#方法:

protected virtual async Task<bool> DeployStoredProcedure(string storedProcedureName)
{
    try
    {
        var dir = Directory.GetFiles(this.StoredProceduresPath);
        string storedProceduresPath = Directory.GetFiles(this.StoredProceduresPath).Where(x => x.Contains(storedProcedureName)).First();
        string storedProcedureScriptFull = File.ReadAllText(storedProceduresPath);

        SqlCommand insertProcedureCommand = new SqlCommand(storedProcedureScriptFull, this.SqlConnection)
                {
                    CommandType = CommandType.Text,
                    CommandTimeout = this.CommandTimeout
                };

        await this.EnsureConnectionOpened();
        await insertProcedureCommand.ExecuteNonQueryAsync();

        return true;
    }
    catch (Exception exception)
    {
        this.SqlConnection.Close();
        ExceptionDispatchInfo.Capture(exception).Throw();
        return false;
    }
}

通常来说,它将存储过程脚本读取为字符串,然后尝试像通常的SQL查询一样执行它。一切正常,直到到达

await insertProcedureCommand.ExecuteNonQueryAsync();

并引发异常

  

'GO'附近的语法不正确
  CREATE / ALTER PROCEDURE”必须是查询批处理中的第一条语句。

我注意到,如果存储过程没有这部分

IF (OBJECT_ID('sp_InsertDevice', 'P') IS NOT NULL) 
    DROP PROCEDURE sp_InsertDevice 
GO

不会引发异常,并且该过程将成功部署。因此问题可以表述为:如何通过C#代码部署包含(IF EXISTS-DROP)逻辑的存储过程?

PS。我知道我可以在另一个SQL脚本中通过c#删除存储过程,但我想在一个脚本中执行。还请注意,我有SQL Server 2014,而不是像2016这样的较新版本,等等(由于我的公司,我知道那很烂)

3 个答案:

答案 0 :(得分:3)

使用动态SQL的解决方法:

IF (OBJECT_ID('sp_InsertDevice', 'P') IS NOT NULL) 
    DROP PROCEDURE sp_InsertDevice 

EXEC(
'CREATE PROCEDURE sp_InsertDevice
    @serialNumber nvarchar(8),
    @modelName nvarchar(40),
    @userId int
AS
BEGIN
    INSERT INTO Device (SerialNumber, ModelName, UserID)
    VALUES (@serialNumber, @modelName, @userId)

    SELECT CAST(SCOPE_IDENTITY() AS INT);
END');

db<>fiddle demo


不幸的是,您必须将存储过程中的每个'加倍。 T-SQL尚不支持here-strings i T-SQL

答案 1 :(得分:1)

GO不是有效的SQL命令,用于将SQL分成由SQL Server管理器在服务器上顺序执行的部分。

但是,使用SQL Server生成的脚本分发数据库方案非常容易。

因此,我将自己生成的SQL脚本拆分为“ GO”关键字,并一一执行。

类似这样的东西(这是一些“很旧的”代码的副本,因此您应该使用var等对其进行清理):

                Regex regex = new Regex("GO\r\n",RegexOptions.Singleline);
                ArrayList updateCommands = new ArrayList(regex.Split(updateScript));

                using (SqlConnection con = GetNewConnection()) {
                    con.Open();
                    foreach(string commandText in updateCommands) {
                        if (string.IsNullOrWhiteSpace(commandText)) continue;
                        using (SqlCommand cmd = new SqlCommand(commandText, con)) {
                            cmd.ExecuteNonQuery();
                        }
                    } // foreach
                }

答案 2 :(得分:0)

请考虑使用SQL Server Management Objects。与SqlClient不同,SMO包含用于识别GO批量分隔符的方法。 SMO作为NuGet package可用。

下面是一个示例控制台应用程序,该应用程序使用GO方法使用Microsoft.SqlServer.Management.Common.ServerConnection.ExecuteNonQuery批处理分隔符执行脚本。

using System;
using System.Data;
using System.Data.SqlClient;
using Microsoft.SqlServer.Management.Common;

class Example
{

    static void Main(string[] args)
    {
            var script = @"
IF (OBJECT_ID('sp_InsertDevice', 'P') IS NOT NULL) 
    DROP PROCEDURE sp_InsertDevice 
GO
CREATE PROCEDURE sp_InsertDevice
    @serialNumber nvarchar(8),
    @modelName nvarchar(40),
    @userId int
AS
BEGIN
    INSERT INTO Device (SerialNumber, ModelName, UserID)
    VALUES (@serialNumber, @modelName, @userId)

    SELECT CAST(SCOPE_IDENTITY() AS INT);
END
";
        try
        {
            using (var connection = new SqlConnection("Data Source=YourServer;Integrated Security=SSPI;Initial Catalog=YourDatabase"))
            {
                var serverConnection = new ServerConnection(connection);
                connection.Open();

                serverConnection.ExecuteNonQuery(script);
            }
        }
        catch
        {
            throw;
        }
    }
}