在实体框架中调用多个SaveChanges

时间:2012-10-26 07:14:16

标签: entity-framework-4 transactions transactionscope

我正在构建自己的自定义存储库,基于实体框架,我正在创建一些扩展方法,允许我将部分视图模型保存为实体模型,因此我正在构建自己的添加和更新方法。

目前,每个方法都有来自DbContext的SaveChanges(),在末尾调用,这意味着每个模型都会调用一个调用。

我正在为MVC4网站构建这个基本DAL模式,这意味着大多数时候我将访问1个模型,但事实并非如此。

在更新3个实体时为每个模型调用SaveChanges()是不是太糟糕了,或者我应该先将所有内容添加到对象上下文中,还是将SaveChanges()作为某种事务提交来进行?

5 个答案:

答案 0 :(得分:65)

我知道这是一个迟到的答案,但我觉得分享很有用。

现在在 EF6 中,使用dbContext.Database.BeginTransaction()

更容易理解这一点 像这样:

using (var context = new BloggingContext())
{
    using (var dbContextTransaction = context.Database.BeginTransaction())
    {
        try
        {
            // do your changes
            context.SaveChanges();

            // do another changes
            context.SaveChanges();

            dbContextTransaction.Commit();
        }
        catch (Exception ex)
        {
            //Log, handle or absorbe I don't care ^_^
        }
    }
}

有关详细信息,请查看this

再次出现在EF6 Onwards

答案 1 :(得分:6)

当相关实体应该在单个事务中持久化时,多次调用SaveChanges(没有事务范围)是一种不好的做法。你创建的是漏洞抽象。创建一个单独的工作单元类或使用ObjectContext/DbContext本身。

答案 2 :(得分:3)

我强烈建议不要在每个方法中调用SaveChanges()。使用存储库模式和工作单元是更好的前进方法。工作单元允许您更有效地使用db调用,并且还可以帮助您防止在某些数据无效时污染数据库(例如,用户详细信息正常,但地址失败)。

这是一个很好的教程来帮助你。

http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

答案 3 :(得分:1)

这是使用我目前使用的UnitOfWork处理多个context.SaveChanges()的另一种方法。

我们持有所有context.SaveChanges()方法,直到调用最后一个方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace DataAccess
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly Context context;
        private readonly Dictionary<Type, object> repositories = new Dictionary<Type, object>();

        private int beginChangeCount;
        private bool selfManagedTransaction = true;

        public UnitOfWork(Context context)
        {
            this.context = context;
        }     

        //Use generic repo or init the instance of your repos here
        public IGenericRepository<TEntity> GetRepository<TEntity>() where TEntity : BaseEntityModel
        {
            if (repositories.Keys.Contains(typeof(TEntity)))
                return repositories[typeof(TEntity)] as IGenericRepository<TEntity>;

            var repository = new Repository<TEntity>(context);
            repositories.Add(typeof(TEntity), repository);

            return repository;
        }

        public void SaveChanges()
        {           
            if (selfManagedTransaction)
            {
                CommitChanges();
            }
        }

        public void BeginChanges()
        {
            selfManagedTransaction = false;
            Interlocked.Increment(ref beginChangeCount);
        }

        public void CommitChanges()
        {
            if (Interlocked.Decrement(ref beginChangeCount) > 0)
            {
                return;
            }

            beginChangeCount = 0;
            context.SaveChanges();
            selfManagedTransaction = true;
        }
    }
}

使用样品。

在下面的代码中找到我的评论

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;

namespace BusinessServices.Domain
{
    public class AService : BaseBusinessService, IAService
    {
        private readonly IBService BService;
        private readonly ICService CService;
        private readonly IUnitOfWork uow;

        public AService (IBService BService, ICService CService, IUnitOfWork uow)
        {
            this.BService = BService;
            this.CService = CService;
            this.uow = uow;
        }

        public void DoSomeThingComplicated()
        {
            uow.BeginChanges();

            //Create object B - already have uow.SaveChanges() inside
            //still not save to database yet
            BService.CreateB();

            //Create object C  - already have uow.SaveChanges() inside
            //still not save to databse yet
            CService.CreateC();

            //if there are no exceptions, all data will be saved in database
            //else nothing in database
            uow.CommitChanges();

        }
    }
}

答案 4 :(得分:0)

在这种情况下建议采用articulated here这种新的现代方法。

如果您熟悉TransactionScope课程,那么您已经知道如何使用DbContextScope。它们本质上非常相似 - 唯一的区别是DbContextScope创建和管理DbContext实例而不是数据库事务。但就像TransactionScopeDbContextScope是环境的,可以嵌套,可以禁用其嵌套行为,并与异步执行流程一起正常工作。

public void MarkUserAsPremium(Guid userId)  
{
    using (var dbContextScope = _dbContextScopeFactory.Create())
    {
        var user = _userRepository.Get(userId);
        user.IsPremiumUser = true;
        dbContextScope.SaveChanges();
    }
}

DbContextScope中,您可以通过两种方式访问​​作用域管理的DbContext个实例。您可以通过DbContextScope.DbContexts属性获取它们:

public void SomeServiceMethod(Guid userId)  
{
    using (var dbContextScope = _dbContextScopeFactory.Create())
    {
        var user = dbContextScope.DbContexts.Get<MyDbContext>.Set<User>.Find(userId);
        [...]
        dbContextScope.SaveChanges();
    }
}

但这当然只能在创建DbContextScope的方法中使用。如果您需要访问其他任何地方的环境DbContext实例(例如,在存储库类中),您可以依赖于IAmbientDbContextLocator,您可以这样使用:

public class UserRepository : IUserRepository  
{
    private readonly IAmbientDbContextLocator _contextLocator;

    public UserRepository(IAmbientDbContextLocator contextLocator)
    {
        if (contextLocator == null) throw new ArgumentNullException("contextLocator");
        _contextLocator = contextLocator;
    }

    public User Get(Guid userId)
    {
        return _contextLocator.Get<MyDbContext>.Set<User>().Find(userId);
    }
}