Entity Framework: Single Database, Multiple Contexts, Receiving Transactions

I would like to use multiple contexts / schemas in my current project, but I'm not sure enough about the correct way to transfer write accesses to contexts in a single transaction.

I understand that there are two ways to achieve this: DbContext.Database.BeginTransaction()

and TransactionScope

. I'm not sure if I am using them correctly and / or if there are other ways to do this.

Example:
Suppose the following model / context:

public class A
{
   [Key]
   public int Id { get; set; }
   public string Name { get; set; }
}

public class ContextA : DbContext
{
   public ContextA() : base( "MultipleContextsTest" ) { }

   public DbSet<A> SetA { get; set; }

   protected override void OnModelCreating( DbModelBuilder modelBuilder )
   {
      modelBuilder.HasDefaultSchema( "SchemaA" );
      base.OnModelCreating( modelBuilder );
   }
}

      

Then let's take the second model / context:

public class B
{
   [Key]
   public int Id { get; set; }
   public string OtherName { get; set; }
}

public class ContextB : DbContext
{
   public ContextB() : base( "MultipleContextsTest" ) { }

   public ContextB( DbConnection conn, bool ownsConnection = false ) 
     : base( conn, ownsConnection ) { }

   public DbSet<B> SetB { get; set; }

   protected override void OnModelCreating( DbModelBuilder modelBuilder )
   {
      modelBuilder.HasDefaultSchema( "SchemaB" );
      base.OnModelCreating( modelBuilder );
   }
} 

      

Then my approach using is DbContext.Database.BeginTransaction()

as follows:

void Using_BeginTransaction()
{
   using( ContextA contextA = new ContextA() )
   {
      using( var transaction = contextA.Database.BeginTransaction() )
      {
         contextA.SetA.Add( new A { Name = "Name" } );
         contextA.SaveChanges();

         using( ContextB contextB 
           = new ContextB( transaction.UnderlyingTransaction.Connection ) )
         {
            contextB.Database.UseTransaction( transaction.UnderlyingTransaction );
            contextB.SetB.Add( new B() { OtherName = "OtherName" } );
            contextB.SaveChanges();
         }

         transaction.Commit();
      }
   }
}

      

What concerns me the most is that I have not been able to find an example for reusing an "outer" context join for an "inner" context. The documentation says that the transaction is "external" and in the examples I've found so far, the transaction was not used to transfer between contexts. Either the transaction was used to start SQLCommand

, or the transaction was derived from SqlConnection

, externally created and explicitly.
In other words: I am asking if I am abusing a function that cannot be intended to be used in this way. It could also be because neither a connection nor a transaction can be passed directly into the "internal" context, but their "base" versions must be used.

My approach to use is TransactionScope

as follows:

void Using_TransactionScope()
{
   using( TransactionScope transaction = new TransactionScope() )
   {
      using( ContextA contextA = new ContextA() )
      {
         contextA.SetA.Add( new A { Name = "Name" } );
         contextA.SaveChanges();
      }

      using( ContextB contextB = new ContextB() )
      {
         contextB.SetB.Add( new B() { OtherName = "OtherName" } );
         contextB.SaveChanges();
      }

      transaction.Complete();
   }
}

      

Here my problems are mostly related to MSDTC (Microsoft Distributed Transaction Coordinator) i.e. avoid a transaction raised to a distributed transaction.
It looks like the transaction won't be promoted if everything is correct:

  • one database is used
  • the contexts use the same connection string
  • contexts use the same connection (not 100% sure about this)

Is it guaranteed, or can it be achieved, that the contexts use the same connection?

However, what concerns me the most is the comments in line with the accepted answer to this question: One transaction with multiple dbcontexts . These comments could mean that the transaction is being promoted to a "lightweight" distributed transaction (whatever is). In any case, the commentator considered this approach very dangerous. It's right? But then, what would be the goal TransactionScope

in the first place?

As a side note: I did some rough performance measurements (using 5000 calls for each of the two methods above) and found:

  • BeginTransaction()

    slightly faster than TransactionScope

    .
  • Both versions are clearly faster than no transaction. I didn't expect this. Perhaps this is because only one connection needs to be created?
+3


source to share


1 answer


I've done this for multiple procs in the same context, never in two separate contexts. It should work the same way, although in theory:

using (var ee = new EntitiesTest())
using (var transaction = new TransactionScope())
{
   ee.spMoneyUpdate(  ...)
   ee.spUpdateTotals();
   transaction.Complete();
}

      



If the volume is not complete, it should roll back. Meaning I want everything or nothing. If the first stored procedure fails, I do NOT want the next one to run and the first one to rollback. My understanding on TransactionScope is this.

0


source







All Articles