实体框架:单个数据库,多个上下文,在正确处理事务方面

3
我希望在当前项目中使用多个上下文/模式,但我不确定如何正确地将对上下文的写访问包装在单个事务中。我的理解是有两种方法可以实现这一点:DbContext.Database.BeginTransaction()TransactionScope。我不确定我是否正确地使用了它们,或者是否还有其他方法可以做到这一点。
例如:假设以下模型/上下文:
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 );
   }
}

假设有第二个模型/上下文(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 );
   }
} 

那么,使用 DbContext.Database.BeginTransaction() 方法的我的方法如下:

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();
      }
   }
}

我最担心的是找不到一个关于如何在“内部”上下文中重用“外部”上下文连接的示例。文档中说事务是“外部的”,但我迄今为止找到的示例中,事务并没有被用于在上下文之间传递。要么使用事务来运行SQLCommand,要么从外部显式地创建并接收SqlConnection的事务。
换句话说:我想知道是否在误用可能不打算以这种方式使用的功能。这也可能暗示着既不能直接将连接和事务传递给“内部”上下文,而必须使用它们的“基础”版本。

我使用TransactionScope的方法如下:

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();
   }
}

这里我主要关注于 MSDTC (Microsoft Distributed Transaction Coordinator),即避免将事务升级为分布式事务。
如果满足以下所有条件,则似乎事务不会被升级:

  1. 只使用单个数据库
  2. 上下文使用相同的连接字符串
  3. 上下文使用相同的连接(不确定是否100%正确)

能否保证上下文使用相同的连接,还是可以实现这一点?

然而,我最担心的是在 Stack Overflow 问题One transaction with multiple dbcontexts的接受答案下面的评论。这些评论可能意味着事务被升级为“轻量级”分布式事务(不知道这是什么)。无论如何,评论者都认为这种方法非常危险。这正确吗?但是,首先 TransactionScope 的目的是什么?

顺便说一下:我进行了一些简单的性能测试(对每个以上两种方法进行了5000次调用),发现:

  1. BeginTransaction()TransactionScope 稍快。
  2. 两个版本都比不使用事务的情况明显快。我没有想到。也许是因为只需要创建一个连接?
1个回答

0

我曾经为同一上下文中的多个过程完成了此操作,但从未针对两个不同的上下文。理论上应该是一样的:

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

如果范围未能完成,任何一个都应该回滚。这意味着我要么全是,要么全不是。如果第一个存储过程失败,我不希望下一个存储过程被触发并且第一个回滚。我的理解是TransactionScope可以做到这一点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接