Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement ambient transaction in Entity Framework Core?

I need to realize transaction under two models (using two separated bounded contexts). So code like this:

  using (TransactionScope scope = new TransactionScope())
  {  
       //Operation 1
        using(var context1 = new Context1())
       {
           context1.Add(someCollection1);
           context1.SaveChanges();
       }
       //Operation 2
       using(var context2 = new Context2())
       {
           context2.Add(someCollection2);
           context2.SaveChanges();
       }

       scope.Complete();
   }

return the exception:

An ambient transaction has been detected. Entity Framework Core does not support ambient transactions. See http://go.microsoft.com/fwlink/?LinkId=800142

In Link they advice to use one connection for two context. And use context2 in using block of context1.

But if I use own controller/service for every model:

using (TransactionScope scope = new TransactionScope())
{  
      service1.DoWork();
      service2.DoWork();

       scope.Complete();
 }

How should I implement this? Add connection as parameter in method - seems absurdly. Init service with connection also bad idea.

like image 821
error Avatar asked Aug 28 '17 12:08

error


3 Answers

You can use 'contexts' like this:

using (var context1 = new Context1())
{
    using (var transaction = context1.Database.BeginTransaction())
    {
        try
        {
            context1.Add(someCollection1);
            context1.SaveChanges();

            // if we don't have errors - next step 
            using(var context2 = new Context2())
            {
               // second step
               context2.Add(someCollection2);
               context2.SaveChanges();
            }   

            // if all success - commit first step (second step was success completed)
            transaction.Commit();
        }
        catch (Exception)
        {
            // if we have error - rollback first step (second step not be able accepted)
            transaction.Rollback();
        }
    }
}

If you will be use many controllers/services, than you can pass DbConnection into your methods of services with using internal methods for this operation. You must encapsulate logic of low layer.

Init service with connection also bad idea. - may be you right. But you can try init two methods with one connection and single transaction.

See next answers, they can help you:

  • Nested DbContext due to method calls - Entity Framework
  • One transaction with multiple dbcontexts
  • Entity Framework Core - Using Transactions
like image 71
Denis Bubnov Avatar answered Nov 03 '22 14:11

Denis Bubnov


Use below snipet

using (var context = new MyContext())
{
using (var transaction = context.Database.BeginTransaction())
{
    try
    {
        var customer = context.Customers
            .Where(c => c.CustomerId == 2)
            .FirstOrDefault();
            customer.Address = "43 rue St. Laurent";

        context.SaveChanges();
         
        var newCustomer = new Customer
        {
            FirstName = "Elizabeth",
            LastName = "Lincoln",
            Address = "23 Tsawassen Blvd."
        };
         
        context.Customers.Add(newCustomer);
        context.SaveChanges();

        transaction.Commit();
    }
    catch (Exception)
    {
        transaction.Rollback();
    }
}
}

More Details: https://entityframeworkcore.com/saving-data-transaction

like image 40
Rejwanul Reja Avatar answered Nov 03 '22 14:11

Rejwanul Reja


To your statement of How should I implement this?:

using (TransactionScope scope = new TransactionScope())
{  
      service1.DoWork();
      service2.DoWork();

      scope.Complete();
 }

I am using a Controller > Service > Repository pattern, and I have found the same error is resolved for me when utilizing the async option. Each repository has a db context injected in Startup.cs following the usual approach. Perhaps this was fixed in a newer version of .NET Core, or maybe I am just using the same context instance across repositories? The following works for me:

using (TransactionScope scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{  
      service1.DoWork();
      service2.DoWork();

      scope.Complete();
 }

like image 28
Thomas Avatar answered Nov 03 '22 15:11

Thomas