C# 是否可以在一个操作中执行多个存储过程?

C# 是否可以在一个操作中执行多个存储过程?,c#,.net,stored-procedures,ado.net,C#,.net,Stored Procedures,Ado.net,我正在编码读取xml文件以更新数据库。我得到大约500个xml文件,我想尽快处理它们 所有数据库操作都是使用存储过程完成的 每个xml文件大约需要35个不同的存储过程 最初我是这样写代码的 var cmd = new SqlCommand("EXEC UpdateTeamStats("+teamId+","+points+")"); cmd.CommandType = CommandType.Text; 但经过一些最佳实践后,我将其改为 var cmd = new SqlCommand("Up

我正在编码读取xml文件以更新数据库。我得到大约500个xml文件,我想尽快处理它们

所有数据库操作都是使用存储过程完成的

每个xml文件大约需要35个不同的存储过程

最初我是这样写代码的

var cmd = new SqlCommand("EXEC UpdateTeamStats("+teamId+","+points+")");
cmd.CommandType = CommandType.Text;
但经过一些最佳实践后,我将其改为

var cmd = new SqlCommand("UpdateTeamStats");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("teamId", 21);
cmd.Parameters.Add("points", 2);
由于从程序中调用了大量的存储过程,我意识到我必须进行较少的调用才能进行优化

所以我想收集所有35个存储过程,并一次性执行它们

存储过程因参数不同而不同,我不知道在我上面所做的参数更改之后如何收集和执行它们

我曾考虑调用一个巨大的存储过程,在该存储过程中调用其他35个,但我不太擅长SQL,这将导致不必要的复杂性

完全用C#做这件事有可能吗


或者是否有其他更好的方法来排队存储过程并快速运行它们

您可以通过创建一个
命令队列
,并在命令上创建一个委托,该委托映射到您对存储过程调用的任何要求;从外观上看,类似于:

public class CommandQueue
{
    private Connection _connexion = new Connection(); // Set this up somehow.

    // Other methods to handle the concurrency/ calling/ transaction etc.

    public Func<string, Dictionary<string, int>, bool> CallStoredProcedure = (procedureName, parameterValueMap) =>
    {
      cmd.Connection = GetConnexion();
      var cmd = new SqlCommand(procedureName);
      cmd.CommandType = CommandType.StoredProcedure;

      foreach (var parameterValueMapping in parameterValueMap)
      {
        cmd.Parameters.Add(parameterValueMapping.Key, parameterValueMapping.Value);
      }

      var success = cmd.ExecuteNonQuery();

      return success;
    }

    private Connection GetConnexion()
    {
      return _connexion;
    }
}
公共类命令队列
{
私有连接_connexion=new Connection();//以某种方式进行设置。
//处理并发/调用/事务等的其他方法。
public Func callstored过程=(过程重命名,参数值映射)=>
{
cmd.Connection=GetConnexion();
var cmd=新的SqlCommand(procedureName);
cmd.CommandType=CommandType.storedProcess;
foreach(parameterValueMap中的var parameterValueMapping)
{
cmd.Parameters.Add(parameterValueMapping.Key,parameterValueMapping.Value);
}
var success=cmd.ExecuteNonQuery();
回归成功;
}
私有连接GetConnexion()
{
返回连接;
}
}
然后,设置
命令队列
,这样您就有了一个线程池,您可以从中调用新线程上的委托,以便它们并行运行

实际上,看看这个类,您可以对它进行异步调用。因此,您应该能够异步调用每个存储过程,为每个存储过程完成时设置一个委托,并将其全部打包到事务中,这样您就可以在需要时通过在每个命令上调用
Cancel()
回滚它们。我可能仍然会使用
CommandQueue
将其抽象出来,因为我建议您以后可能会更改它


我认为我仍然应该在
CommandQueue
上用委托封装存储过程调用,这样就可以抽象出存储过程的细节,让其他人更容易理解该过程,也更容易维护。如果您添加新的存储过程,或者更改名称,或者做些其他事情,就会容易得多。您可以设置包含所有委托的静态列表,或包含必要存储过程详细信息的静态列表,并使用委托只传入参数。

请从

好的,但是我怎么用呢

这个包装类的用法非常简单

DAC DC = new DAC();
DC.StoredProcedure = "nProc_InsertOrder";
DC.Params.Add("@OrderId", SqlDbType.VarChar, "Order1" );
DC.Params.Add("@CustomerName", SqlDbType.VarChar, "test");
DAC.Commands.Add(DC);

DC = new DAC();
DC.StoredProcedure = "nProc_InsertOrderLineItems";
DC.Params.Add("@OrderId", SqlDbType.VarChar, "Order1" );
DC.Params.Add("@OrderLineId", SqlDbType.VarChar, "A1");
DAC.Commands.Add(DC);

DC = new DAC();
DC.StoredProcedure = "nProc_InsertOrderLineItems";
DC.Params.Add("@OrderId", SqlDbType.VarChar, "Order1" );
DC.Params.Add("@OrderLineId", SqlDbType.VarChar, "A2");
DAC.Commands.Add(DC);

DC = new DAC();
DC.StoredProcedure = "nProc_CreateBill";
DC.Params.Add("@BillDate", SqlDbType.DateTime, DateTime.Now);
DC.Params.Add("@BillId", SqlDbType.VarChar, "Bill1");
DAC.Commands.Add(DC);
DAC.ExecuteBatch();
如果订单插入失败,则不应创建票据。同样,如果行项目失败,则不应创建订单。我们通过ADO.Net只用几行代码就可以实现这一点


在本例中,在调用ExecuteBatch之前,我们实际上并没有插入记录,而是准备对象进行批量更新。

imo的最佳解决方案是编写一个存储过程,其中传入一个表值参数,其中包含每个xml文件的所有参数列表。然后在此存储过程中,为表值参数中的每条记录调用所有其他存储过程


如果这不正常,那么您可以使用text类型的SqlCommand而不是存储过程,只需边执行边生成命令即可。您可以像现在一样使用参数,也可以只编写动态sql。

就我个人而言,根据我使用ADO.NET的经验,我认为使用单个
将它们作为单独的语句执行不会有任何问题

这样做的好处是利用.NET的惊人功能,使您能够单独使用/自定义/交互每个命令,并使用共享连接(将连接代理的数量减少到微不足道的数量)

我还想强调
在这里使用
子句的重要性,这有助于正确处理各种资源

例如:

using (var conn = new SqlConnection("connection string"))
{
    using (var cmd = new SqlCommand())
    {
        cmd.Connection = conn;
        cmd.CommandType = CommandType.StoredProcedure;

        //ready to query 
        conn.Open();

        cmd.CommandText = "UpdateTeamStats";
        var teamIdParam = new SqlParameter("teamId", 21);
        var pointsParam = new SqlParameter("points", 2);

        cmd.Parameters.Add(teamIdParam);
        cmd.Parameters.Add(pointsParam);

        cmd.ExecuteNonQuery(); //OR if you're async cmd.ExecuteNonQueryAsync();

        //the rest of your executions

        conn.Close();
    }
}
如果我有点担心的话,你可以使用一个图书馆,就像我的一样,它可以将上述内容简化为:

using (var db = new DbConnect("connection string"))
{
    db.SetSqlCommand("UpdateTeamStats");
    db.AddParameter("teamId", 21);
    db.AddParameter("points", 2);

    db.ExecuteNonQuery().Wait(); //OR if youre async await db.ExecuteNonQuery();

    db.ClearParameters();
    db.SetSqlCommand("some other proc");

    //rest of executions
}

当你在执行过程中发生错误时,你期望发生什么?所有执行的过程都会回滚吗?或者继续处理剩下的过程?注意,你的标题是“并行”,但读到你的问题,你的意思似乎是“作为一个数据库命令”@GeorgeDuckett-这是我的编辑。最初的是“一次过”。John,当发生一个错误时,我想执行回滚George,是的,我的意思是在一次操作中,主DAC对象来自哪里-我们用于
DAC.Commands.Add的对象?它不可能是静态的,否则就不会是线程安全的!这真的做到了我们想要的吗?如果没有SQL Server客户端类的显式支持,这真的只是执行一个DB往返,还是将所有命令包装在一个事务中?我们将所有命令包装在一个对象中,并在与事务的单个打开连接中执行它们。