忽略特定查询中的TransactionScope

31

我正在寻找一种在TransactionScope存在的情况下执行查询并忽略TransactionScope的方法 - 基本上,我想无论如何都执行此特定查询。

我正在使用EF Code-First,并且应用程序的设计方式是,在单个调用期间多次打开新数据上下文,每个上下文都有自己的更改,并且所有这些更改都包含在单个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 方法,我更愿意只在一个地方处理这个问题。

3个回答

46

如果您在启用了suppress选项的另一个事务范围内包装日志调用,则不会使用事务范围。

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和Entity Framework中都可以工作。谢谢。 - Joe Enos
刚刚实现了这个功能,现在出现了这个错误:分布式事务管理器(MSDTC)的网络访问已被禁用。请使用组件服务管理工具,在 MSDTC 的安全配置中启用 DTC 以进行网络访问。我读到说打开多个事务可能会导致此错误? - Dominic Cotton
这真的为我省了很多麻烦!我正在事务中处理MSMQ消息,但需要访问SQL Azure获取一些额外的数据,而他们不支持DTC。使用这个使我的事务仍然可以工作,同时也让Azure正常运行。 - Scott Salyer

1
我发现了一个解决方案,虽然不是很满意,但似乎可以工作。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;
    }
}

1
那绝对有一种“恶心”的感觉,但如果它能工作,那就行了。有点像我提出的单例建议。我也不喜欢它,但它肯定会起作用。 - Joe Brunscheon
@JoeBrunscheon +1 对于“不好看”的支持 :) - Joe Enos
在调试时没问题,但在发布模式或运行时却不起作用 :( - mostafa8026

1

我的初步想法是,您需要将LogRepo放在自己的DataContext(DC2)上,这样周围的TransactionScope(使用DC1)不会在未提交时将其回滚。

基本上,您需要使记录自包含和原子化。

编辑 继续查看后,如果您将Logging从SaveChanges中移出并放在DoSomething()的catch()中,您的Logging似乎可以工作。但是,您的Logging仍然需要自包含和原子化。


TransactionScope会将其包装在其中的每个数据上下文周围 - LogRepo确实会创建自己全新的上下文,但仍然会被TransactionScope捕获。 - Joe Enos
针对您的编辑 - 我提到了 - 我需要至少在 SaveChanges 内部构建错误消息,因为我需要访问打开的数据上下文,但即使我从那里构建消息并将其抛到调用者,我仍然必须在调用者中执行记录操作多达几十次,而我正试图将所有这些内容都放在一个地方。 - Joe Enos
1
是的,很明显你的实现无法按照设计要求工作。另一个选择是将LogRepo构造为单例,在应用程序启动时创建,使其拥有自己的DBContext而在所有事务范围之外创建。虽然这不是最干净的设计,但它可以解决TransactionScope的问题。我还没有找到一种方式来从TransactionScope中“取消注册”上下文... - Joe Brunscheon
1
谢谢 - 我不认为让整个应用程序保持开放上下文是我感到舒适的,但我会考虑一下。 - Joe Enos

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