0

Working on a WebApi project that's backed by mssql with EntityFramework, and also Oracle (12c) using oracle's ManagedDataAccess.Client.OracleConnection. We use autofac to inject an instance of our context per request, but all oracle access is just done ad hoc.

We have certain operations that depend on both databases at the same time, so we opted to use the TransactionScope object to manage the transaction.

For the most part it works well, the light weight transactions that are promoted to distributed work great. But there is one issue I've encountered after completing a distributed transaction.

Given:

public void Test() 
{
    var preItem = new HelpItem
    {
        Field1 = "pre batch";
    };
    _context.Items.Add(preItem);
    _context.SaveChanges(); // This save always works.

    var batchResult = FooService.BatchOperation(true);

    var postItem = new HelpItem
    {
        Field1 = "post batch";
    };
    _context.Items.Add(postItem);
    _context.SaveChanges(); // This will succeed/fail depending on whether FooService caused a distributed transaction.
}

With the BatchOperation method as:

public Result BatchOperation(bool triggerDtc) 
{
    using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
    {
        if (triggerDtc){
            // Make requests to both databases.
        } else {
            // Make request to one database.
        }

        // Always complete for the sake of the demonstration.
        transaction.Complete();
    }
}

If a distributed transaction is encountered and then completed & fully disposed EF doesn't seem to be able to recover and go back to working as it was before the transaction came into play.

The error:

Distributed transaction completed. Either enlist this session in a new transaction or the NULL transaction.

What would be the correct way to handle this?

For this particular case you can simply create another transaction around the second part:

var batchResult = FooService.BatchOperation(true);

using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    var postItem = new HelpItem
    {
        Field1 = "post batch";
    };
    _context.Items.Add(postItem);
    _context.SaveChanges(); // This save depends on whether FooService caused a distributed transaction.
    transaction.Complete();
}

But this issue came up because the FooService.BatchOperation method was altered with just a lookup to the other database, unknowingly breaking every method out there that continues to use the context after calling it. With normal transaction a single EF context can freely be used in and out of them without issue, is there any way to achieve the same with a distributed transaction?

EDIT: This really just has me confused now. Just the act of making a request in another (non distributed) transactionscope is enough to restore EF functionality.

public IHttpActionResult Test() 
{
    var preItem = new HelpItem
    {
        Field1 = "pre batch";
    };
    _context.Items.Add(preItem);
    _context.SaveChanges(); // This save works.

    var batchResult = FooService.BatchOperation(true);

    using (var transaction = new new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
    {
        var lookupAnything = _context.Items.ToList();
        transaction.Complete(); // This is optional, because we really don't care and it's disposed either way.
    }

    var postItem = new HelpItem
    {
        Field1 = "post batch";
    };
    _context.Items.Add(postItem);
    _context.SaveChanges(); // Now this always works.
}

Obviously I can't just go around putting this everywhere, so still not sure what the actual solution is.

JWrightII
  • 942
  • 11
  • 26
  • The context 's connection exists and is used before the transaction scope is created and after it was disposed. I learned [the hard way](http://stackoverflow.com/a/11420408/861716) that this causes trouble. – Gert Arnold May 02 '17 at 20:39
  • That is actually incorrect. And you can definitely create the transaction after the fact. See this gist for example: https://gist.github.com/johnhwright/ea4c7da66ceaf36f47dd671b430956fb I'm guessing the issue you ran into in your link was simply because you weren't specifying the isolation level when creating a new TransasctionScope so it was using the default "Serializable" level (yikes), which being different than mssql/ef's "ReadCommitted", prevented automatic enlisting. – JWrightII May 02 '17 at 21:33

0 Answers0