【问题标题】:How to deploy stored procedure via C# code?如何通过 C# 代码部署存储过程?
【发布时间】:2020-04-14 16:19:50
【问题描述】:

我有一个类似的存储过程

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。我知道我可以通过 c# 在另一个 SQL 脚本中删除存储过程,但我想在一个脚本中执行它。另请注意,我有 SQL Server 2014,而不是像 2016 等较新的版本(因为我的公司,我知道它很烂)

【问题讨论】:

  • 仅供参考,您应该避免使用 sp_ 前缀;它是 Microsoft 为 Special 程序保留的,因此您的 SP 可能有一天会突然停止工作(如果 Microsoft 引入了一个名为您的 SP 的特殊程序),并且使用 sp_ 会导致性能下降:@ 987654321@
  • 如果您在 Visual Studio 中创建一个数据库项目,然后部署生成的 DACPAC,而不是尝试通过代码这样做,那会好得多。这与避免使用诸如 FTP 之类的蛮力技术通过使用 MSDeploy 来部署网站本质上是相同的。此外,部署未知 string 的内容可以说与代码中嵌入的 SQL 语句一样危险(如果不是更多的话)

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


【解决方案1】:

使用动态 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

【讨论】:

    【解决方案2】:

    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
                    }
    

    【讨论】:

      【解决方案3】:

      考虑使用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;
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多