事务范围(TransactionScope)是否适用于预先存在的连接?

7

我有这样的一段代码:

try
{
    using (TransactionScope scope = new TransactionScope())
    {
        some_db_function();

        for (i = 0; i < 10; i++)
        {
            some_other_db_function();
        }

        scope.Complete();
    }
}
catch (Exception ex)
{
   MessageBox.Show(ex.Message + " all done transactions will rollback");   
}

在数据库函数内部,类似这样的操作会发生:

private void some_db_functions()
{
    using (TransactionScope scope = new TransactionScope())
    {
       //some processing on db
       scope.Complete();
    }
}

根据预期,如果数据库事务中出现任何问题,例如在函数中插入或更新时发生错误,则到目前为止已完成的所有事务都会被回滚。但实际情况并非如此;尽管它抛出异常且父函数中的scope.Complete()从未被触发,但仍然没有任何事务被回滚。

问题出在哪里?


你其实不需要使用 scope.Complete(),在 using 语句中使用 Transaction scope 就可以照顾所有事情。 - Flowerking
你的数据库方法是打开一个连接还是使用现有的连接? - Mitch Wheat
即使没有scope.Complete(),它仍然无法正常工作。 - Siyavash
它们都使用先前声明的相同连接。 - Siyavash
你不需要在 some_db_functions 中添加单独的 TransactionScope,因为该方法仍然属于初始 TransactionScope 的范围。可能发生的情况是该方法提交了事务,但调用者抛出了异常,但调用者的事务中没有任何内容可以回滚。 - David Esteves
2
@Flowerking 不,你需要在成功的情况下显式地调用 Complete()。如果你不调用 Complete(),它总是会在 Dispose()(通常通过 using)中回滚。 - Marc Gravell
2个回答

17
如果已经存在打开的连接,它不会自动注册到环境事务中。您需要明确设置它。
暗示注册连接不受支持。要在事务范围内注册,请执行以下操作:
在事务范围内打开连接。
或者,如果连接已经打开,请调用连接对象上的EnlistTransaction方法。 Ref.
这将使现有连接注册:
connection.EnlistTransaction(Transaction.Current)

14
IIRC,自动注册到环境事务是在连接创建/打开时发生的;如果您在事务范围内创建连接,则应该一切正常。 然而:

它们都使用先前声明的相同连接

如果连接存在于事务之外,则不会注册。

最佳实践是仅在一个工作单元周围创建/打开连接,而不是永久性的(并且:让连接池执行其工作)。如果遵循该实践,它应该可以正常工作。 所以:

这样做行不通:

using(var conn = CreateAndOpenConnection()) {
    // ...
    using(var tran = new TransactionScope()) {
        SomeOperations(conn);
        tran.Complete();
    }
    // ...
}

而这个则应该可以工作:

using(var tran = new TransactionScope()) {
    // ...
    using(var conn = CreateAndOpenConnection()) {
        SomeOperations(conn);
    }
    tran.Complete();
    // ...
}

@Mitch 鉴于我们的帖子之间有大约30秒的时间差,我相信我们是同时在打字。是的,我们同意。 - Marc Gravell
那么,每个函数中的scope.Complete()都是必需的吗? - Siyavash
每个子方法都应该有一个TransactionScope,而且每个子方法都需要有一个scope.complete()吗? - Siyavash
1
@Siyavash 这取决于您是否希望单独调用这些子方法。最简单的方法是,您可以只使用封装(最外层)事务。但是,如果您确实有内部事务,则是的:它们都需要在被处理之前完成,否则它们会立即使最外层事务失败。 - Marc Gravell
1
@Siyavash,它确切地在哪里说了?如果你指的是这个例子:那是为了说明你可以嵌套事务,如果你这样做:本质上最外层的事务控制“提交”,而任何失败都控制“中止”。您不需要在多个级别使用事务范围,实际上,TransactionScope最有用的功能之一是它自动适用于嵌套代码,无需显式实现与事务相关的任何更改。 - Marc Gravell
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接