30

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

目前,每个方法最后都会调用 DbContext 中的 SaveChanges(),这意味着对于每个模型,都会调用一个调用。

我正在为 MVC4 站点构建这个基本 DAL 模式,这意味着大多数时候我将访问 1 个模型,但不一定是这种情况。

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

4

5 回答 5

79

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

现在在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 ^_^
        }
    }
}

有关更多信息,请查看

又是在 EF6 以后

于 2014-04-26T09:21:26.093 回答
8

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

于 2012-10-26T07:55:06.780 回答
3

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

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

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-应用程序

于 2012-10-26T11:04:48.750 回答
2

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

我们将保留所有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();

        }
    }
}
于 2017-07-27T10:21:25.050 回答
0

在这种情况下,建议采用此处阐述的新现代方法。

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

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

在 aDbContextScope中,您可以通过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);
    }
}
于 2016-04-08T16:01:59.880 回答