10

我已经做了很多搜索,但找不到直接的答案。

我有两个存储过程,它们都是导入到 DBContext 对象的函数

  1. 插入A()
  2. 插入B()

我想把它们放在一个交易中。(即如果 InsertB() 失败,则回滚 InsertA())

我怎么做?我可以只声明一个 TransactionScope 对象并环绕这两个存储过程吗?

谢谢

4

2 回答 2

15

您需要在事务范围内登记您的操作,如下所示:

using(TransactionScope tranScope = new TransactionScope()) 
{
  InsertA();
  InsertB();

  tranScope.Complete();
}

出错时,事务范围将自动回滚。当然,您仍然需要处理异常并执行您的异常处理设计所要求的任何事情(日志等)。但除非您手动调用,否则在作用域结束Complete()时事务会回滚。using

除非您在同一事务范围内打开其他数据库连接,否则事务范围不会提升为分布式事务(参见此处)。

了解这一点很重要,因为否则您需要在涉及此操作的所有服务器(Web、中间层、sql server)上配置MSDTC 。因此,只要不将事务提升为分布式事务,就可以了。

注意: 为了微调您的事务选项,例如超时和隔离级别,请查看TransactionScope构造函数。默认隔离级别是可序列化的。

附加样本: 这里

于 2012-06-25T17:28:08.160 回答
4

您可以使用 TransactionScope 对象,也可以使用 SqlConnection.BeginTransaction 方法。使用 TransactionScope 时要小心,在不同数据库中调用存储过程时,可以将事务转为分布式事务。分布式事务可能是资源密集型的。

如何使用 sqlConnection.BeginTransaction...(http://msdn.microsoft.com/en-us/library/86773566.aspx)

private static void ExecuteSqlTransaction(string connectionString)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();

        SqlCommand command = connection.CreateCommand();
        SqlTransaction transaction;

        // Start a local transaction.
        transaction = connection.BeginTransaction("SampleTransaction");

        // Must assign both transaction object and connection
        // to Command object for a pending local transaction
        command.Connection = connection;
        command.Transaction = transaction;

        try
        {
            command.CommandText =
                "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')";
            command.ExecuteNonQuery();
            command.CommandText =
                "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')";
            command.ExecuteNonQuery();

            // Attempt to commit the transaction.
            transaction.Commit();
            Console.WriteLine("Both records are written to database.");
        }
        catch (Exception ex)
        {
            Console.WriteLine("Commit Exception Type: {0}", ex.GetType());
            Console.WriteLine("  Message: {0}", ex.Message);

            // Attempt to roll back the transaction.
            try
            {
                transaction.Rollback();
            }
            catch (Exception ex2)
            {
                // This catch block will handle any errors that may have occurred
                // on the server that would cause the rollback to fail, such as
                // a closed connection.
                Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
                Console.WriteLine("  Message: {0}", ex2.Message);
            }
        }
    }
}

如何使用 TransactionScope...(http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx)

// This function takes arguments for 2 connection strings and commands to create a transaction 
// involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the 
// transaction is rolled back. To test this code, you can connect to two different databases 
// on the same server by altering the connection string, or to another 3rd party RDBMS by 
// altering the code in the connection2 code block.
static public int CreateTransactionScope(
    string connectString1, string connectString2,
    string commandText1, string commandText2)
{
    // Initialize the return value to zero and create a StringWriter to display results.
    int returnValue = 0;
    System.IO.StringWriter writer = new System.IO.StringWriter();

    try
    {
        // Create the TransactionScope to execute the commands, guaranteeing
        // that both commands can commit or roll back as a single unit of work.
        using (TransactionScope scope = new TransactionScope())
        {
            using (SqlConnection connection1 = new SqlConnection(connectString1))
            {
                // Opening the connection automatically enlists it in the 
                // TransactionScope as a lightweight transaction.
                connection1.Open();

                // Create the SqlCommand object and execute the first command.
                SqlCommand command1 = new SqlCommand(commandText1, connection1);
                returnValue = command1.ExecuteNonQuery();
                writer.WriteLine("Rows to be affected by command1: {0}", returnValue);

                // If you get here, this means that command1 succeeded. By nesting
                // the using block for connection2 inside that of connection1, you
                // conserve server and network resources as connection2 is opened
                // only when there is a chance that the transaction can commit.   
                using (SqlConnection connection2 = new SqlConnection(connectString2))
                {
                    // The transaction is escalated to a full distributed
                    // transaction when connection2 is opened.
                    connection2.Open();

                    // Execute the second command in the second database.
                    returnValue = 0;
                    SqlCommand command2 = new SqlCommand(commandText2, connection2);
                    returnValue = command2.ExecuteNonQuery();
                    writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
                }
            }

            // The Complete method commits the transaction. If an exception has been thrown,
            // Complete is not  called and the transaction is rolled back.
            scope.Complete();

        }

    }
    catch (TransactionAbortedException ex)
    {
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
    }
    catch (ApplicationException ex)
    {
        writer.WriteLine("ApplicationException Message: {0}", ex.Message);
    }

    // Display messages.
    Console.WriteLine(writer.ToString());

    return returnValue;
}
于 2012-06-25T17:58:21.337 回答