【问题标题】:Creating on-the-fly SQL stored procedure in C# then select it and then delete it在 C# 中创建即时 SQL 存储过程,然后选择它,然后将其删除
【发布时间】:2017-03-17 18:33:20
【问题描述】:

大家好,我正在想办法解决这个问题。我想向我的 C# 程序中的查询发送一个参数,它是我的表的名称。我读到这是不可能的,他们建议您创建一个存储过程来执行此操作。

到目前为止,这是我的代码:

CREATE PROCEDURE _tmpSP 
@TableName NVARCHAR(128) 
AS 
BEGIN 
  SET NOCOUNT ON;
  DECLARE @Sql NVARCHAR(MAX);
SET @Sql = N'SELECT TOP 1 HelpMsg FROM ' + QUOTENAME(@TableName)
 EXECUTE sp_executesql @Sql
 DROP PROCEDURE [_tmpSP]
END

当我在 Server Management Studio 中执行它时,它会创建 SP,但不会执行该存储过程,也不会删除它。

当我在 Server Management Studio 中运行该 SP 时(右键单击在可编程性下dbo._tmpSP并选择执行存储过程)并给它表名,它填充然后删除SP。这是我想要无需进行 2 次查询的最终结果。

SP 运行时的 SQL 查询如下(tHelp 是表名):

USE [TTool]
GO    
DECLARE @return_value int    
EXEC    @return_value = [dbo].[_tmpSP]
        @TableName = N'tHelp'    
SELECT  'Return Value' = @return_value    
GO

我收到返回的帮助消息,也返回值 0。

如何修改此 SP 才能做到这一点?

【问题讨论】:

  • 你为什么要创建一个存储过程,这样你就可以执行一次然后删除它?这根本没有任何意义。为什么需要存储过程?当然,创建程序的脚本不会运行它。
  • @SeanLange 因为我无法做到这一点:SELECT TOP 1 HelpMsg FROM @val1
  • 这是什么?一个自毁过程 :) 我原以为查询处理器会拒绝删除它当前正在执行的过程。
  • 那是因为你不能参数化对象名称。但说真的,这有什么意义呢?参数化的主要思想之一是防止 sql 注入,而您创建的代码甚至不会假装避免这种情况。事实上,您已经通过使用它来构建一个动态字符串来执行该参数的全部意义。只需创建您的动态字符串并执行它。但请保留引号名称。您甚至可以检查 sys.objects 以确保该值是对象名称。
  • 除了这里提出的问题外,还可能存在设计缺陷。 TOP 1 不是确定性的。所以要么这些表每个只包含一行,那么你最好将所有这些帮助文本合并到一个带有一些有意义的键的表中,或者你冒着每次查询运行时读取不同行的风险。

标签: c# sql-server stored-procedures sql-server-2012


【解决方案1】:

只需这样做,忘记存储过程:

EXECUTE sp_executesql 'SELECT TOP 1 HelpMsg FROM '+QUOTENAME(@TableName)

肮脏的 C#...

string qry = string.Format("SELECT TOP 1 HelpMSG FROM {0}", myTableName.Replace("'", "''"));
cmd = conn.CreateCommand();
cmd.CommandText = qry;
string helpMsg = conn.ExecuteScalar();

其中connSystem.Data.SqlClient.SqlConnection 的一个实例

【讨论】:

  • 那就更简单了
  • 您需要将该表名包装在quotename中以最大程度地降低sql注入的风险。
  • 更好的办法是在 select 语句中加上一个存在来查看 sys.objects 以确保 myTableName 实际上是一个表名。
  • 您在上次更新时破坏了您的答案。您不能参数化对象名称。这就是现在 sql 的工作方式。
  • 不在任何已知的 C# 版本中。 GC 调用终结器,但不处理。超出范围也不行。另见here
【解决方案2】:

我同意@SsJVasto。如果您仍然需要不在 C# 程序中对查询进行硬编码,您可以使用 xml 并将查询保留在其中。并获取 xml 并执行查询。我猜你想处理一些动态的东西。

【讨论】:

    【解决方案3】:

    这样做是没有意义的,因为它相当复杂,而且还会产生创建和删除存储过程的开销。如果你有一个动态查询处理一些不能作为参数推送的动态元素,你可以构造查询字符串:

    var query = $"SELECT TOP 1 col FROM {tableName}";
    

    但是,如果 tableName 是基于用户输入构造的,则必须小心避免 SQL 注入。 This question and its answers处理这个问题:

    DbConnection connection = GetMyConnection(); 
    DbProviderFactory factory = DbProviderFactories.GetFactory(connection);
    DbCommandBuilder commandBuilder = factory.CreateCommandBuilder();
    var tableName commandBuilder.QuoteIdentifier(rawTableName);
    

    如果需要“普通”(非表名)参数,请照常在查询中传递它们。例如。 @param1,@param2

    【讨论】:

    • 所以我会使用 {@val1} 而不是 @val1?
    • @StealthRT - 不,应该是 {yourVariableHoldingCurrentTableName}。如果表名是根据用户输入计算的,请不要错过答案的 SQL 注入部分。
    【解决方案4】:

    您需要创建另一个 SP 来应用您的逻辑。首先让我们看看你的SP:

    CREATE PROCEDURE [_tmpSP]
    @TableName NVARCHAR(128) 
    AS 
     BEGIN 
      DECLARE @Sql NVARCHAR(MAX);
      SET @Sql = N'SELECT TOP 1 HelpMsg FROM ' + @TableName
      EXEC(@Sql)
    END
    

    只有在返回结果后需要删除第一个 SP 时才创建另一个 SP。逻辑是:

    Create procedure auto_delete
    @NewTableName
    as
    begin
    
    EXEC _tmpSP @TableName = @NewTableName
    
    Drop procedure [_tmpSP]
    
    End
    

    在 C# 中(我假设您使用的是上面的第二个 SP):

    你的代码可能是这样的:

    ..
    using System.Data.SqlClient;
    ..
    string a = YourTableName;        
    using (SqlConnection sqlCon = new SqlConnection(YourDatabaseConnection))
    {
     sqlCon.Open()
    using (SqlCommand sqlCmd = sqlCon.CreateCommand())
    {
    sqlCmd.CommandText = "auto_delete";
    sqlCmd.CommandType = CommandType.StoredProcedure;
    sqlCmd.Parameters.Add(new SqlParameter("NewTableName", a));
    sqlCmd.ExecuteNonQuery();
    }   
     sqlCon.Close();
     }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-09
      相关资源
      最近更新 更多