C# – Ignore TransactionScope for specific query

centity-frameworkentity-framework-5transactionstransactionscope

I'm looking for a way to execute a query while a TransactionScope is alive, and ignore the TransactionScope – basically, I want to execute this particular query no matter what.

I'm using EF code-first, and the way the application is designed, a new data context is opened many times throughout a single call, each with its own changes, and all of those are contained within a single TransactionScope, which has Complete() called at the end assuming no failures. Inside of the context we've overridden SaveChanges so that if any exception happens on base.SaveChanges(), we can catch it and log to the database before rolling back the transaction.

Since the SaveChanges happens inside the transaction, the logging obviously doesn't happen, because it belongs to the same transaction as the original call. I'm trying to ignore the TransactionScope altogether just for the logging code.

Here's some stripped-down code:

// 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
    }
}

I tried using regular ADO.NET instead of EF for the logging, but still the same results – it gets rolled back too.

I need the error handling to happen inside of SaveChanges, because what I'm logging is the state of the entities that are being saved – so I can't just easily move the logging somewhere else. I could build the message while inside the SaveChanges catch, and throw it and let DoSomething catch log it, but there are dozens of DoSomething methods, and I'd much rather just deal with this in one place.

Best Answer

If you wrap your log call inside of another transaction scope with the suppress option enabled, transaction scope will not be used.

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;
    }
}
Related Topic