【问题标题】:Ignore TransactionScope for specific query忽略特定查询的 TransactionScope
【发布时间】:2013-08-17 21:42:10
【问题描述】:

我正在寻找一种在 TransactionScope 处于活动状态时执行查询的方法,并忽略 TransactionScope - 基本上,无论如何我都想执行这个特定的查询。

我使用 EF 代码优先,以及应用程序的设计方式,在一次调用中多次打开新的数据上下文,每次都有自己的更改,所有这些都包含在单个 TransactionScope 中,假设没有失败,最后调用了Complete()。在上下文中,我们覆盖了SaveChanges,因此如果base.SaveChanges() 发生任何异常,我们可以在回滚事务之前捕获它并记录到数据库。

由于SaveChanges 发生在事务内部,因此显然不会发生日志记录,因为它与原始调用属于同一事务。我试图完全忽略 TransactionScope,只是为了记录代码。

这是一些精简的代码:

// From the context
public override int SaveChanges() {
    try {
        return base.SaveChanges();
    } catch (Exception ex) {

        // Writes to the log table - I want this to run no matter what
        LogRepo.Log(/*stuff to log from the context*/);

        throw;
    }
}

// Inside the business logic
public void DoSomething() {
    try {
        using (var scope = new TransactionScope()) {

            using (var context = new FooContext()) {
                // Do something
                context.SaveChanges();
            }
            using (var context = new FooContext()) {
                // Do something else
                context.SaveChanges();
            }

            scope.Complete();
        }
    } catch (Exception ex) {
        // scope.Complete is never called, so the transaction is rolled back
    }
}

我尝试使用常规 ADO.NET 而不是 EF 进行日志记录,但结果仍然相同 - 它也会回滚。

我需要在SaveChanges 内部进行错误处理,因为我正在记录的是正在保存的实体的状态 - 所以我不能轻易地将记录移动到其他地方。我可以在SaveChanges catch 中构建消息,然后将其抛出并让DoSomething catch 记录它,但是DoSomething 方法有很多,我宁愿只在一个地方处理。

【问题讨论】:

    标签: c# entity-framework transactions entity-framework-5 transactionscope


    【解决方案1】:

    如果您在启用抑制选项的情况下将日志调用包装在另一个事务范围内,则不会使用事务范围。

    public override int SaveChanges() {
        try {
            return base.SaveChanges();
        } catch (Exception ex) {
            using (var scope = new TransactionScope(TransactionScopeOption.Suppress)) {
                LogRepo.Log(message); // stuff to log from the context
            }
    
            throw;
        }
    }
    

    【讨论】:

    • 完美!正是我想要的。经过测试,它适用于常规 ADO.NET 和实体框架。谢谢。
    • 刚刚实现了这个,现在我得到这个错误:分布式事务管理器(MSDTC)的网络访问已被禁用。请使用组件服务管理工具在 MSDTC 的安全配置中启用 DTC 以进行网络访问。我读到打开多个交易会导致这个错误?
    • 这让我省了很多麻烦!我正在事务中处理 MSMQ 消息,但需要与 SQL Azure 联系以获取一些额外数据,并且它们不支持 DTC。使用它可以使我的事务仍然有效,同时也让 Azure 发挥作用。
    【解决方案2】:

    只是我最初的想法,但您需要将 LogRepo 放在它自己的 DataContext (DC2) 上,以便周围的 TransactionScope(使用 DC1)在未提交时不会回滚。

    基本上,您需要使您的日志记录自包含且原子化。

    编辑 再看一遍,在我看来,如果您将 Logging 从 SaveChanges 移到 DoSomething() 上的 catch() 中,您的日志记录将起作用。但是,您的日志记录仍然需要是独立的和原子的。

    【讨论】:

    • TransactionScope 将自己包裹在其中的每个数据上下文中 - LogRepo 确实创建了自己的全新上下文,但仍被 TransactionScope 拾取。
    • 对于您的编辑-我提到过-我至少需要从SaveChanges 内部构建错误消息,因为我需要访问开放数据上下文,但即使我从那里构建消息并且把它扔给调用者,我仍然需要在调用者中执行几十次日志记录,我试图将它全部保存在一个地方。
    • 是的,很明显您的实现不会按设计工作。另一种选择是使用您的 LogRepo 并使其成为单例,在应用程序启动时创建,并在所有事务范围之外创建自己的 DBContext。不是最简洁的设计,但它会让你了解这个 TransactionScope 业务。我还没有找到从 TransactionScope 中“取消登记”上下文的方法......
    • 谢谢 - 我不认为我对在应用程序的整个生命周期中保持开放上下文感到满意,但我会考虑的。
    【解决方案3】:

    我找到了一个我不太满意但似乎可行的解决方案。 TransactionScope 显然只影响当前线程,因此使用新线程进行日志记录似乎可以正常工作。

    public override int SaveChanges() {
        try {
            return base.SaveChanges();
        } catch (Exception ex) {
    
            string message = /*stuff to log from the context*/;
            new Thread(msg => {    
    
                LogRepo.Log(msg);
    
            }).Start(message);
    
            throw;
        }
    }
    

    【讨论】:

    • 这肯定有一种“恶心”的感觉,但如果它有效,它就有效。有点像我的单身建议。我也不喜欢它,但它肯定会奏效。
    • @JoeBrunscheon +1 for "icky" :)
    • 我调试的时候没问题。但在发布模式或运行中它不起作用:(
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-11
    • 1970-01-01
    • 1970-01-01
    • 2011-05-05
    • 2018-04-18
    • 1970-01-01
    • 2011-02-12
    相关资源
    最近更新 更多