【问题标题】:Nested Transaction Behavior in EF6EF6 中的嵌套事务行为
【发布时间】:2015-12-08 15:29:56
【问题描述】:

我目前正在使用 TransactionScope 来管理我的数据层中的事务,但我遇到了嵌套事务和异步的问题,即在嵌套事务期间连接似乎关闭或事务被提升为 MSDTC。我还没有找到确切的问题,但在阅读之后,看起来这种情况不支持particuarly well,我应该改用 Database.BeginTransaction()。

我的问题是我找不到有关 Database.BeginTransaction() 如何处理嵌套事务的信息,特别是在我想要使用环境事务而不是创建新事务的情况下。我的怀疑是它不打算以这种方式工作,如果我想管理嵌套事务,我应该抽象出事务管理以给予我更多控制权。

不想添加不必要的抽象层我想知道是否有人在这方面有经验并且可以确认 Database.BeginTransaction() 嵌套在另一个事务中时的行为?

关于我的 DAL 的其他信息:基于 CQS 模式,我倾向于将与 Db 相关的代码封装在命令或查询处理程序中,因此这个嵌套如何发生的简化/人为示例如下:

public class AddBlogPostHandler
{
    private readonly MyDbContext _myDbContext;

    public AddBlogPostHandler(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }

    public async Task ExecuteAsync(AddBlogPostCommand command)
    {
        using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
            // .. code to create and add a draft blog post to the context
            await _myDbContext.SaveChangesAsync();

            var publishBlogPostCommand = new PublishBlogPostCommand();
            // ..set some variables on the PublishBlogPostCommand
            await PublishBlogPostAsync(command);

            scope.Complete();
        }
    }
}

public class PublishBlogPostHandler
{
    private readonly MyDbContext _myDbContext;

    public PublishBlogPostHandler(MyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }

    public async Task ExecuteAsync(PublishBlogPostCommand command)
    {
        using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
        {
            // .. some code to do one set of update
            await _myDbContext.SaveChangesAsync();

            // .. some other db updates that need to be run separately
            await _myDbContext.SaveChangesAsync();

            scope.Complete();
        }
    }
}

【问题讨论】:

    标签: c# entity-framework transactions entity-framework-6


    【解决方案1】:

    从内部事务可以独立提交或回滚的意义上说,不存在嵌套事务。嵌套事务实际上只维护一个引用计数。在最后一次提交时,我们得到一个物理提交。在第一次回滚时,我们得到一个物理回滚。只要确保你知道这一点。

    避免使用 MSDTC 很重要。这对于TransactionScopeBeginTransaction 都是可能的。对于前者,您需要在范围内显式地Open 连接,以便 EF 不会一直打开新连接。

    正如您在本期中所读到的,这是 EF 中的一个缺陷(L2S 没有)。请花时间对问题发表评论,以确保团队知道客户遇到了这个问题。

    特别是在我想要使用环境事务而不是创建新事务的情况下。

    这非常适合TransactionScope。我认为您切换到BeginTransaction 是基于误解。也许你可以在 cmets 中澄清一下。

    当嵌套在另一个事务中时,确认 Database.BeginTransaction() 的行为

    在第一段中解释。

    关于我的 DAL 的其他信息:基于 CQS 模式,我倾向于将与 Db 相关的代码封装在命令或查询处理程序中,因此这个嵌套如何发生的简化/人为示例如下:

    除了缺少db.Connection.Open() 调用(如上所述)之外,代码看起来很好。

    此模式将支持在同一事务中执行多个查询和命令。只需将另一个范围包裹在它周围。确保不要打开连接两次,例如在采取行动之前检查conn.State

    【讨论】:

    • 我想我真的在问对BeginTransaction 的嵌套调用是创建一个新事务(相当于TransactionScopeOption.RequiresNew)还是使用环境事务(相当于TransactionScopeOption.Required)?除了我无法让它工作之外,切换到BeginTransaction 是基于它是他们对 EF6+ 的推荐。我愿意尝试让它与TransactionScope 一起工作,但如果我手动打开连接,我会在外部事务完成之前遇到异常(事务“已完成但尚未处理”)
    • BeginTransaction 类似于必需。没有办法在同一个连接上有多个 trans。因此,它不可能表现得像 RequiresNew。 being their recommendation for EF6 我也读过,但我认为没有给出任何理由。奇怪的建议。 i get an exception before the outer transaction has completed 我想你应该调查并修复这个错误。如果您只需要修复一个小错误,为什么要放弃整个方法?
    • 太好了,我不知道每个连接只能有一个事务。我只是无法TransactionScope 在我的场景中工作,所以我最终编写了一个小抽象,这意味着我可以嵌套事务范围并仍然使用Database.BeginTransaction 来打开底层事务。谢谢你的帮助@usr
    • 听起来 BeginTransaction 的行为更像是“RequiresNew”而不是“Required”。 'RequiresNew' 行为是在已经有一个活动的情况下抛出异常(因为你'需要一个新的')。这就是 BeginTransaction 所做的。如果你已经调用了 BeginTransaction,再次调用它会抛出一个错误,就像用 RequiresNew 构造一个 TransactionScope 一样。
    • @Triynko 你确定 BeginTransaction 在调用两次时会抛出吗?我不这么认为,这样做没有意义。它会破坏嵌套。 RequiresNew 绝对不会抛出。它创建一个新事务并在范围退出时恢复旧事务。
    猜你喜欢
    • 2016-09-09
    • 1970-01-01
    • 2011-02-09
    • 1970-01-01
    • 2016-02-19
    • 2017-02-22
    • 1970-01-01
    • 1970-01-01
    • 2011-06-25
    相关资源
    最近更新 更多