Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ignore TransactionScope for specific query

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.

like image 557
Joe Enos Avatar asked Aug 15 '13 16:08

Joe Enos


People also ask

What is the use of TransactionScope in C#?

The TransactionScope class provides a simple way to mark a block of code as participating in a transaction, without requiring you to interact with the transaction itself. A transaction scope can select and manage the ambient transaction automatically.

What is ambient transaction C#?

System.Transactions defines a concept called an ambient transaction. The ambient transaction is the transaction that is present in the thread that the current application code is executing within.

What is transaction scope in SQL Server?

The TransactionScope class makes a code block transactional by implicitly enlisting connections in a distributed transaction. You must call the Complete method at the end of the TransactionScope block before leaving it. Leaving the block invokes the Dispose method.

What is transaction scope in Entity Framework?

Entity Framework is already operating within a TransactionScope. The connection object in the transaction passed is null. That is, the transaction is not associated with a connection – usually this is a sign that that transaction has already completed.


3 Answers

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;
    }
}
like image 178
habermanm Avatar answered Oct 19 '22 09:10

habermanm


Just my initial thought, but you need to put your LogRepo on it's own DataContext (DC2) so that the surrounding TransactionScope (with DC1) won't roll it back when it's not committed.

Basically, you need to make your logging self-contained and atomic.

EDIT In looking at it some more, it seems to me that if you moved your Logging out from the SaveChanges into the catch() on DoSomething(), your logging would work. But, your logging still needs to be self-contained and atomic.

like image 34
Joe Brunscheon Avatar answered Oct 19 '22 10:10

Joe Brunscheon


I found one solution that I'm not really happy with, but seems to work. TransactionScope apparently only affects the current thread, so logging using a new thread seems to work ok.

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;
    }
}
like image 1
Joe Enos Avatar answered Oct 19 '22 08:10

Joe Enos