将 TransactionScope 与实体框架 6 一起使用

Posted

技术标签:

【中文标题】将 TransactionScope 与实体框架 6 一起使用【英文标题】:Using TransactionScope with Entity Framework 6 【发布时间】:2016-03-09 02:49:27 【问题描述】:

我无法理解的是是否可以对上下文进行更改并在提交之前在同一个事务中获取更改。

这就是我要找的:

using (var scope = new TransactionScope(TransactionScopeOption.Required)) 
 
    using (var context = new DbContext()) 
     
        //first I want to update an item in the context, not to the db
        Item thisItem = context.Items.First();
        thisItem.Name = "Update name";
        context.SaveChanges(); //Save change to this context

        //then I want to do a query on the updated item on the current context, not against the db
        Item thisUpdatedItem = context.Items.Where(a=>a.Name == "Update name").First();

        //do some more query
     

    //First here I want it to commit all the changes in the current context to the db
    scope.Complete(); 
 

有人可以帮我理解并展示一个工作模式吗?

【问题讨论】:

【参考方案1】:

是的,当您想要将实体插入数据库并使用自动生成的 id 进行下一次插入或更新时,这是可以做到的,并且非常有用

using (var context = new DbContext())     
 
    using (var transaction = context.Database.BeginTransaction()) 
        var item = new Item();
        context.Items.Insert(item);
        context.SaveChanges(); // temporary insert to db to get back the auto-generated id

        // do some other things
        var otherItem = context.OtherItems.First();
        // use the inserted id
        otherItem.Message = $"You just insert item with id = item.Id to database";
        transaction.Commit();
    
 

因为您的问题还询问了工作模式,所以这是我的工作代码(使用 FluentApi、DbContext 和事务)。我和你有同样的问题:)。希望对你有帮助

public class FluentUnitOfWork : IDisposable

    private DbContext Context  get; 

    private DbContextTransaction Transaction  get; set; 

    public FluentUnitOfWork(DbContext context)
    
        Context = context;
    

    public FluentUnitOfWork BeginTransaction()
    
        Transaction = Context.Database.BeginTransaction();
        return this;
    

    public FluentUnitOfWork DoInsert<TEntity>(TEntity entity) where TEntity : class
    
        Context.Set<TEntity>().Add(entity);
        return this;
    

    public FluentUnitOfWork DoInsert<TEntity>(TEntity entity, out TEntity inserted) where TEntity : class
    
        inserted = Context.Set<TEntity>().Add(entity);
        return this;
    

    public FluentUnitOfWork DoUpdate<TEntity>(TEntity entity) where TEntity : class
    
        Context.Entry(entity).State = EntityState.Modified;
        return this;
    

    public FluentUnitOfWork SaveAndContinue()
    
        try
        
            Context.SaveChanges();
        
        catch (DbEntityValidationException dbEx)
        
            // add your exception handling code here
        
        return this;
    

    public bool EndTransaction()
    
        try
        
            Context.SaveChanges();
            Transaction.Commit();
        
        catch (DbEntityValidationException dbEx)
        
            // add your exception handling code here
        
        return true;
    

    public void RollBack()
    
        Transaction.Rollback();
        Dispose();
    

    public void Dispose()
    
        Transaction?.Dispose();
        Context?.Dispose();
    

示例用法:

var status = BeginTransaction()
                // First Part
                .DoInsert(entity1)
                .DoInsert(entity2)
                .DoInsert(entity3)
                .DoInsert(entity4)
                .SaveAndContinue()
                // Second Part
                .DoInsert(statusMessage.SetPropertyValue(message => message.Message, $"Just got new message entity1.Name"))
            .EndTransaction();

【讨论】:

这会在数据库中插入行,然后如果事务被回滚,它会删除该行吗?或者你如何获得行的ID? @kienct89 在调用transaction.Commit()之前不会在db中插入行,id会临时分配到内存中 好的,谢谢你的例子和描述。我会对此进行测试并回复您 TransactionScope 回答 Database.BeginTransaction 的问题。他是如何真正接受的? TransactionScope 有答案吗? 好主意!想到 DoBulkInsert 也会很棒sensibledev.com/entity-framework-bulk-insert【参考方案2】:

如果您想确保只查询上下文的本地内容,您可以使用“本地”集合:

Item thisItem = context.Items.First();  
thisItem.Name = "Update name";    
Item thisUpdatedItem = context.Items.Local.Where(a=>a.Name == "Update name").First();

这只会查询上下文的内存数据,不会命中数据库。 只要您通过添加对象或从数据库加载对象在上下文中具体化对象,“本地”数据就会出现,即您无需调用 SaveChanges()。 SaveChanges() 会将上下文的内容写入您的数据库。

【讨论】:

这看起来很棒。我有一个问题;正如 kienct89 在他的回答中描述的那样,它处理插入并在数据库中自动生成 ID。 “本地”有没有类似的功能? 对于新记录,“本地”只是一个暂存区。 ID 由数据库在插入记录时提供。它是一个数据库函数,而不是一个 EF 函数。【参考方案3】:

根据我的经验,创建上下文不是必需的,我喜欢尽可能简化,因此如果您需要在回滚时触发代码,请在事务周围加上 try catch。

try

   using (var scope = new TransactionScope(TransactionScopeOption.Required))
   
          ...do stuff

          scope.Complete();
   

catch (Exception)

  ...do stuff

【讨论】:

以上是关于将 TransactionScope 与实体框架 6 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

如何在多实例应用程序上处理 TransactionScope?

TransactionScope 与 SQLite 内存数据库和 NHibernate

OracleInternal.MTS.DTCPSPEManager 错误

为啥 TransactionScope 不能与 Sqlite 一起使用?

TransactionScope不能与linux上的async / await方法一起使用

C#分布式事务解决方案-TransactionScope