使用TransactionScope与Entity Framework 6

25

我不明白的是,是否可以在提交之前对上下文进行更改,并在同一事务中获取更改。

这就是我要寻找的内容:

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

有人能帮我理解并展示一个可行的模式吗?

3个回答

52

是的,这是可能的,并且在您想要将实体插入数据库并使用自动生成的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 和 Transaction)。我曾经遇到了和你一样的问题 :)。希望它能帮助到你。

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 - Marcus Höglund
只有在调用transaction.Commit()之后,它才会将行插入到数据库中,id将被临时分配在内存中。 - Kien Chu
好的,谢谢您提供的示例和说明。我会测试一下并回复您。 - Marcus Höglund
3
问题:通过TransactionScope提问,回答是Database.BeginTransaction。他真的接受了吗?是否有TransactionScope的答案? - Nuri YILMAZ
不错的想法!考虑使用DoBulkInsert会更棒,参见https://sensibledev.com/entity-framework-bulk-insert/ - bunjeeb

5

如果您想确保仅查询上下文中的本地内容,则可以使用"local"集合:

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生成的插入操作。 "local"是否有类似的功能? - Marcus Höglund
1
对于新记录,“Local”只是一个暂存区。当记录被插入时,ID由数据库提供。这是一个数据库函数,而不是EF函数。 - Stephan Keller

0
在我的经验中,创建上下文并不是必要的。我喜欢尽可能简化代码,所以如果你需要在回滚事件中触发代码,请将事务包围在 try catch 中。
try
{
   using (var scope = new TransactionScope(TransactionScopeOption.Required))
   {
          ...do stuff

          scope.Complete();
   }
}
catch (Exception)
{
  ...do stuff
}

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