如何在C#.NET(SQL Server)中正确有效地重用准备好的语句?

如何在C#.NET(SQL Server)中正确有效地重用准备好的语句?,c#,sql,.net,sql-server,prepared-statement,C#,Sql,.net,Sql Server,Prepared Statement,我看了很多问题,但显然我的苏福不能胜任这项任务,所以我来了。我试图有效地使用准备好的语句,我不仅仅是指参数化一条语句,而是要编译一条语句以便多次重用。我的问题在于参数和重用以及如何正确实现 通常我遵循以下程序(人为示例): 太好了,行得通。但是,我不希望每次运行此查询时都执行所有这些步骤(或后四个步骤)。这似乎与事先准备好的声明的想法背道而驰。在我看来,我应该只需要更改参数并重新执行,但问题是如何做到这一点 我尝试了s.Parameters.Clear(),但这实际上删除了参数本身,而不仅仅是值

我看了很多问题,但显然我的苏福不能胜任这项任务,所以我来了。我试图有效地使用准备好的语句,我不仅仅是指参数化一条语句,而是要编译一条语句以便多次重用。我的问题在于参数和重用以及如何正确实现

通常我遵循以下程序(人为示例):

太好了,行得通。但是,我不希望每次运行此查询时都执行所有这些步骤(或后四个步骤)。这似乎与事先准备好的声明的想法背道而驰。在我看来,我应该只需要更改参数并重新执行,但问题是如何做到这一点

我尝试了
s.Parameters.Clear()
,但这实际上删除了参数本身,而不仅仅是值,因此我基本上需要重新添加参数并重新准备,这似乎也打破了整个要点。不用了,谢谢

此时,我只剩下遍历
s.Parameters
并将它们全部设置为null或其他值这是否正确?不幸的是,在我当前的项目中,我有大约15个参数的查询,每次运行需要执行大约10000次。我可以将这个迭代分流到一个方法中,但我想知道是否有更好的方法来实现这一点(无需存储过程)

我当前的解决方法是一个扩展方法,
SqlParameterCollection.Nullify
,它将所有参数设置为null,这对我的情况来说很好。我只是在执行后运行这个


我发现了一些几乎相同但尚未回答的问题:

(谢尔盖差一点就要回答了!)

我能找到的最好答案是(1)上面的常识和(2)这一页:


当重新使用准备好的SqlCommand时,您所需要做的肯定是将参数值设置为新值?使用后无需清除

就我自己而言,我还没有看到在过去10年中生产的DBMS从编写语句中获得任何明显的好处(我想如果DB服务器的CPU达到极限,它可能会这样做,但这并不典型)。你确定有必要做准备吗

运行相同的命令“~10000次/次”对我来说有点异味,除非您是从外部源上传的。在这种情况下,批量装载可能会有所帮助?每次跑步都在做什么

干杯-

为了补充Simon的答案,
Command.Prepare()
将改进即席查询的查询计划缓存(通常会编译存储过程)。但是,在中,如果查询是参数化的,也可以缓存参数化的即席查询,从而减少对
Prepare()
的需要

下面是一个保留
SqlParameters
集合的示例,仅更改那些变化的参数值的值,以防止重复创建参数(即保存参数对象创建和集合):

注意事项:

    如果插入大量行,与数据库的其他居民进行并发是很重要的,如果一个酸性事务边界不重要,则可以考虑批处理和提交更新,以便每次一次保存在表上少于5000行锁,以防止表锁升级。
  • 根据proc实际执行的工作,可能有机会并行化循环,例如使用TPL。显然,连接和命令不是线程安全的,每个任务都需要自己的连接和可重用的命令
奇怪,我在编辑框中的第一个链接后有一个换行符,但它没有进入渲染输出。。。已修复。将其放在一个方法上,向其传递一组SqlParameters,然后重用其余部分。:)阅读本文,确保准确定义参数的大小。这将帮助您提高性能。(当然除了storedprocedure)您是否在一个紧密的循环中执行此操作?否则,我会怀疑您可能处于
Prepare()
最理想的场景中。根据我的理解,要想使
Prepare()
有用,您必须保留
SqlCommand
对象,这可能会导致在最终使用完它时处理它时出现问题。@Marciano.Andrade:好主意,另一种使我的无效化的方法。问题更多的是如何重用一个准备好的语句。它基本上是一个周期性的外部加载,来自各种源(SQL、LDAP、SOAP等),通过处理将传入的数据转换为新的结构。不幸的是,它比列映射或选择/插入更复杂。我在其他地方也看到过一些关于准备的答案,在网上阅读了大量资料后,我不相信大多数人会正确地使用准备好的陈述(为了这个目的)。清除它们只是一种预防措施,问题是这(仅重置参数)是否是重新使用命令来保留已编译语句的正确方法。谢谢。您只需要将参数重置为新值(我刚刚测试过,它可以工作)。您不需要将它们设置为null或任何东西。因此,是的,保持连接打开,保持SqlCommands不变,并根据需要重置参数值。感谢注意保持连接打开。我最终创建了一个静态DB类,并用它“注册”了命令,这样在导入的生命周期中,我只有一个连接,可以反复使用这些命令。幸运的是,它是一个单线程应用程序。我还这样做,以便在执行已注册的命令时必须提交参数,并确保它们都存在,这样就不会保留以前的值(它们确实会在我发现的执行之间徘徊)。
SqlConnection db = new SqlConnection(...);
SqlCommand s = new SqlCommand("select * from foo where a=@a", db);
s.Parameters.Add("@a", SqlDbType.VarChar, 8);
s.Prepare();
...
s.Parameters["@a"] = "bozo";
s.Execute();
using (var sqlConnection = new SqlConnection("connstring"))
 {
    sqlConnection.Open();
    using (var sqlCommand = new SqlCommand
       {
          Connection = sqlConnection,
          CommandText = "dbo.MyProc",
          CommandType = CommandType.StoredProcedure,
       })
    {
       // Once-off setup per connection
       // This parameter doesn't vary so is set just once
       sqlCommand.Parameters.Add("ConstantParam0", SqlDbType.Int).Value = 1234;
       // These parameters are defined once but set multiple times
       sqlCommand.Parameters.Add(new SqlParameter("VarParam1", SqlDbType.VarChar));
       sqlCommand.Parameters.Add(new SqlParameter("VarParam2", SqlDbType.DateTime));

       // Tight loop - performance critical
       foreach(var item in itemsToExec)
       {
         // No need to set ConstantParam0
         // Reuses variable parameters, by just mutating values
         sqlParameters["VarParam1"].Value = item.Param1Value; // Or sqlParameters[1].Value
         sqlParameters["VarParam2"].Value = item.Param2Date; // Or sqlParameters[2].Value
         sqlCommand.ExecuteNonQuery();
       }
    }
}