在一个事务中调用多个 SQL Server 存储过程

Posted

技术标签:

【中文标题】在一个事务中调用多个 SQL Server 存储过程【英文标题】:Call multiple SQL Server stored procedures in a transaction 【发布时间】:2013-03-04 02:26:01 【问题描述】:

为了在我当前的项目中使用,我创建了一个允许我异步调用 SQL Server 的类。

我的代码如下所示:

internal class CommandAndCallback<TCallback, TError>

    public SqlCommand Sql  get; set; 
    public TCallback Callback  get; set; 
    public TError Error  get; set; 


class MyCodes:SingletonBase<MyCodes>

    private static string _connString = @"Data Source=MyDB;Initial Catalog=ED;Integrated Security=True;Asynchronous Processing=true;Connection Timeout=0;Application Name=TEST";

    private MyCodes()  

    public void SetSystem(bool production)
    
        _connString =
            string.Format(@"Data Source=MyDB;Initial Catalog=0;Integrated Security=True;Asynchronous Processing=true;Connection Timeout=0;Application Name=TEST", production ? "ED" : "TEST_ED");
    

    public void Add(string newCode, Action<int> callback, Action<string> error)
    
        var conn = new SqlConnection(_connString);
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandTimeout = 0;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandText = @"ADD_CODE";
        cmd.Parameters.Add("@NEW", SqlDbType.NVarChar).Value = newCode;
        cmd.Parameters.Add("@NewId", SqlDbType.Int).Direction = ParameterDirection.Output;

        try
        
            cmd.Connection.Open();
        
        catch (Exception ex)
        
            error(ex.ToString());
            return;
        

        var ar = new CommandAndCallback<Action<int>, Action<string>>  Callback = callback, Error = error, Sql = cmd ;
        cmd.BeginExecuteReader(Add_Handler, ar, CommandBehavior.CloseConnection);
    

    private static void Add_Handler(IAsyncResult result)
    
        var ar = (CommandAndCallback<Action<int>, Action<string>>)result.AsyncState;
        if (result.IsCompleted)
        
            try
            
                ar.Sql.EndExecuteReader(result);
                ar.Callback(Convert.ToInt32(ar.Sql.Parameters["@NewId"].Value));
            
            catch (Exception ex)
            
                ar.Error(ex.Message);
            
        
        else
        
            ar.Error("Error executing SQL");
        
    

public void Update(int codeId, string newCode, Action callback, Action<string> error)
    
        var conn = new SqlConnection(_connString);
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandTimeout = 0;
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandText = @"UPDATE_CODE";
        cmd.Parameters.Add("@CODE_ID", SqlDbType.Int).Value = codeId;
        cmd.Parameters.Add("@NEW", SqlDbType.NVarChar).Value = newCode;

        try
        
            cmd.Connection.Open();
        
        catch (Exception ex)
        
            error(ex.ToString());
            return;
        

        var ar = new CommandAndCallback<Action, Action<string>>  Callback = callback, Error = error, Sql = cmd ;
        cmd.BeginExecuteReader(Update_Handler, ar, CommandBehavior.CloseConnection);
    

    private static void Update_Handler(IAsyncResult result)
    
        var ar = (CommandAndCallback<Action, Action<string>>)result.AsyncState;
        if (result.IsCompleted)
        
            try
            
                ar.Sql.EndExecuteReader(result);
                ar.Callback();
            
            catch (Exception ex)
            
                ar.Error(ex.Message);
            
        
        else
        
            ar.Error("Error executing SQL");
        
    


这可能看起来代码太多,但它让我可以这样称呼它:

private void Add_Click(object sender, EventArgs e)

   MyCodes.Instance.Add("Test",Success,Error)


private void Success(int newId)

   MessageBox.Show(newId.ToString(), "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);


private void Error(string error)

   MessageBox.Show(error, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

上面的代码对我来说很好用,我可以异步执行每个调用。

我现在遇到的问题是将多次调用作为交易进行 - 我想更新 2 个代码并添加一个新代码。

通常我会调用 update,然后在成功的处理程序中调用第二次更新,在第二次更新的处理程序中我会调用 add 来返回新的 id。

类似:

-UPDATE CODE
 |-UPDATE CODE
   |-ADD CODE (only this one return something)

但我想将所有这些都称为事务,因此如果添加代码会中断更新,则会回滚。

问题:

是否可以将多个异步查询作为一个事务调用?

我可以将上述方法称为事务,还是必须创建单独的方法来将我的程序称为一个? (我想避免这个,因为它只是将相同的代码从一种方法复制到另一种方法)

我想补充一点,我使用 .NET 3.5,所以 await 和其他不错的功能不是一个选项。

【问题讨论】:

不幸的是,要将所有过程包装在一个事务中,您必须一个接一个地执行它们。否则每次执行都会有一笔交易。 LukeHennerly - 你能帮我建立一个可以将多个程序调用为一个的方法吗?理想情况下,它需要更新的代码列表和作为参数添加的代码,当然它应该像上面一样被称为异步 【参考方案1】:

是的,这是可能的。在您第一次致电之前,只需致电SqlConnection.BeginTransaction。确保将返回的SqlTransaction 对象分配给链中的每个SqlCommand.Transaction,并在最后调用SqlTransaction.Commit()

【讨论】:

我尝试了一个删除,它必须首先删除一些外键(分离存储过程),但我没有让它运行,但是保存和更新它工作正常【参考方案2】:
public class Command

    public string sql  get; set; 
    public CommandType cmdType  get; set; 
    public Dictionary<string, object> parameter  get; set;  = null;


    private Command insertInvoice(Invoice invoice)
    
        try
        
            Dictionary<string, object> parameterLocal = new Dictionary<string, object>();

            parameterLocal.Add("p_customerId", invoice.customerId);
            parameterLocal.Add("p_invoiceNo", invoice.invoiceNo);
            parameterLocal.Add("p_invoiceDate", invoice.invoiceDate);
            parameterLocal.Add("p_invoiceAmount", invoice.invoiceAmount);                
            parameterLocal.Add("p_withInvoice", invoice.withInvoice);

            return (new Command  sql = "sp_insertInvoice", cmdType = CommandType.StoredProcedure, parameter = parameterLocal );
        
        catch (Exception ex)
        
            throw ex;
        
    

    private Command insertInvoiceModel(InvoiceModel invoiceModel)
    
        try
        
            Dictionary<string, object> parameterLocal = new Dictionary<string, object>();

            parameterLocal.Add("p_invoiceNo", invoiceModel.invoiceNo);
            parameterLocal.Add("p_model", invoiceModel.model);
            parameterLocal.Add("p_quantity", invoiceModel.quantity);
            parameterLocal.Add("p_unitPrice", invoiceModel.unitPrice);

            return (new Command  sql = "sp_insertInvoiceModel", cmdType = CommandType.StoredProcedure, parameter = parameterLocal );
        
        catch (Exception ex)
        
            throw ex;
        
    

 List<Command> commandList = new List<Command>();

 cmd = insertInvoice(invoicesave);

 commandList.Add(cmd);

 cmd = insertInvoiceModel(invoiceModelSave);

 commandList.Add(cmd);

        try
        
            erplibmain.erpDac.runOleDbTransaction(commandList);
        
        catch (Exception ex)
        
            throw ex;
        

    public void runOleDbTransaction(List<Command> commandList)
    
        OleDbConnection erpConnection = new OleDbConnection(ErpDalMain.connectionstring);
        erpConnection.Open();

        OleDbCommand erpCommand = erpConnection.CreateCommand();
        OleDbTransaction erpTrans;

        // Start a local transaction
        erpTrans = erpConnection.BeginTransaction();
        // Assign transaction object for a pending local transaction
        erpCommand.Connection = erpConnection;
        erpCommand.Transaction = erpTrans;

        try
        
            foreach (Command cmd in commandList)
            
                erpCommand.CommandText = cmd.sql;
                erpCommand.CommandType = cmd.cmdType;

                foreach (KeyValuePair<string, object> entry in cmd.parameter)
                
                    erpCommand.Parameters.AddWithValue(entry.Key, entry.Value);
                

                erpCommand.ExecuteNonQuery();

                erpCommand.Parameters.Clear();
            

            erpTrans.Commit();
        
        catch (Exception e)
        
            try
            
                erpTrans.Rollback();
            
            catch (OleDbException ex)
            
                if (erpTrans.Connection != null)
                
                    throw ex;
                
            

            throw e;
        
        finally
        
            erpConnection.Close();
        
    

【讨论】:

【参考方案3】:
  string cnnString =WebConfigurationManager.ConnectionStrings["MyString"].ConnectionString;
    SqlConnection cnn = new SqlConnection(cnnString);
    SqlTransaction transaction;

    cnn.Open();
    transaction = cnn.BeginTransaction();

    try
    

        // Command Objects for the transaction
        SqlCommand cmd1 = new SqlCommand("sproc1", cnn);
        SqlCommand cmd2 = new SqlCommand("sproc2", cnn);

        cmd1.CommandType = CommandType.StoredProcedure;
        cmd2.CommandType = CommandType.StoredProcedure;

        cmd1.Parameters.Add(new SqlParameter("@Param1", SqlDbType.NVarChar, 50));
        cmd1.Parameters["@Param1"].Value = paramValue1;

        cmd1.Parameters.Add(new SqlParameter("@Param2", SqlDbType.NVarChar, 50));
        cmd1.Parameters["@Param2"].Value = paramValue2;

        cmd2.Parameters.Add(new SqlParameter("@Param3", SqlDbType.NVarChar, 50));
        cmd2.Parameters["@Param3"].Value = paramValue3;

        cmd2.Parameters.Add(new SqlParameter("@Param4", SqlDbType.NVarChar, 50));
        cmd2.Parameters["@Param4"].Value = paramValue4;

        cmd1.ExecuteNonQuery();
        cmd2.ExecuteNonQuery();

        transaction.Commit();
    

    catch (SqlException sqlEx)
    
        transaction.Rollback();
    

    finally
    
        cnn.Close();
        cnn.Dispose();
    

【讨论】:

感谢您回答这么老的问题 :) 在空闲时间我会检查您的解决方案。现在我正在将所有内容迁移到 .NET 4.5,因此我将在创建数据库访问类的同时实现此功能。 另外提到SqlCommand.Transaction 这样SqlCommand cmd1 = new SqlCommand("sproc1", cnn, transaction); 或这样cmd1.Transaction = transaction 以防您收到错误消息:ExecuteNonQuery 要求命令在连接分配给命令时具有事务处于待处理的本地事务中。该命令的 Transaction 属性尚未初始化。取自***.com/a/10649035/1369235

以上是关于在一个事务中调用多个 SQL Server 存储过程的主要内容,如果未能解决你的问题,请参考以下文章

Asp.net(C#) 获取 执行sql server 语句/存储过程后的 多个返回值?

SQL Server 数据库的维护(上)_存储过程(procedure)

SQL Server T—SQL 视图 事务

SQL Server 2016 可以在一个事务中重新排列多个查询的顺序吗?

Sql Server - 即使事务回滚也会增加的计数器列

即席只读查询是不是存储在 SQL Server 事务日志中?