仓储模式和工作单元

仓储模式:

  仓储模式源自2004年起的领域驱动设计,它主要在领域层和持久层的提供数据抽象层,是一种数据访问模式,屏蔽底层的存储细节(如:crud的sql详细信息,将这些sql写在另一个类中,以此屏蔽存储细节sql),让我们更关注领域层逻辑(业务逻辑在领域层中)。

   应该为每一个实体提供一个仓储,当我们使用仓储时,就像是在一个集合上进行操作。

  做一个简单的EF Core仓储模式:

  1.首先创建一个通用的仓储接口,提供简单的几个方法,如增删改查。

  public interface IRepository<TEntity> where TEntity : class
    {
        Task AddAsync(TEntity entity);

        Task<TEntity> FindAsync(object key);

        void Delete(TEntity entity);

        Task<IEnumerable<TEntity>> GetAllAsync();

        Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> predicate);

    }

2.实现仓储接口,一个通用的仓储。

    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly DbSet<TEntity> _dbSet;

        public Repository(DbContext dbContext)
        {
            _dbSet = dbContext.Set<TEntity>();
        }

        public async Task AddAsync(TEntity entity)
        {
            await _dbSet.AddAsync(entity);
        }

        public async Task<TEntity> FindAsync(object key)
        {
            return await _dbSet.FindAsync(key);
        }

        public async Task<IEnumerable<TEntity>> GetAllAsync()
        {
            return await _dbSet.ToListAsync();
        }

        public void Delete(TEntity entity)
        {
            _dbSet.Remove(entity);
        }

        public async Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await _dbSet.AnyAsync(predicate);
        }
    }

至此,一个简单的仓储就已经完成了,只需要出入相应的实体和DbContext,new Repository()即可获得相应的仓储。

但是我们不能把提交事务交让每一个仓储自行处理,这将会出现很大的问题,比如同时操作了多个仓储对象时。

这个时候,就需要引入一个新的东西,叫工作单元:UnitOfWork。

工作单元

  工作单元提供了事务的单一原子性,通过事务,一次性提交所以的更改,保证了数据的完整性。

  1.首先创建一个,工作单元接口。

  

    public interface IUnitOfWork<TDbContent> where TDbContent : DbContext
    {
        /// <summary>
        /// 获得IRepository。
        /// </summary>
        /// <typeparam name="TEntity">实体。</typeparam>
        /// <param name="isCustomRepository">是否自定义仓储,如果是,将从ServiceProvider获得。</param>
        /// <returns>返回IRepository。</returns>
        IRepository<TEntity> GetRepository<TEntity>(bool isCustomRepository = false) where TEntity : class;

        /// <summary>
        /// 提交。
        /// </summary>
        /// <returns>修改数量。</returns>
        Task<int> Commit();
    }

二、实现泛型工作单元

    public class UnitOfWork<TDbContext> : IUnitOfWork<TDbContext>, IDisposable where TDbContext : DbContext
    {
        private readonly DbContext _dbContext;

        private readonly Lazy<Dictionary<int, object>> _repositoryContainer;
        public UnitOfWork(TDbContext dbContext)
        {
            _dbContext = dbContext;
            _repositoryContainer = new Lazy<Dictionary<int, object>>();
        }

        public IRepository<TEntity> GetRepository<TEntity>(bool isCustomRepository = false) where TEntity : class
        {
            if (isCustomRepository)
            {
                return _dbContext.GetService<IRepository<TEntity>>();
            }
            else
            {
                var name = typeof(TEntity).FullName.GetHashCode();
                if (!_repositoryContainer.Value.ContainsKey(name))
                {
                    _repositoryContainer.Value.Add(name, new Repository<TEntity>(_dbContext));
                }

                return _repositoryContainer.Value[name] as IRepository<TEntity>;
            }
        }

        public async Task<int> Commit()
        {
            return await _dbContext.SaveChangesAsync();
        }

        #region Dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool isDispose)
        {
            if (isDispose)
            {
                _dbContext.Dispose();
            }
        }

        #endregion
    }

3.在依赖注入中注入

       services.AddScoped<IUnitOfWork<BookDbContext>, UnitOfWork<BookDbContext>>();
            if (!await _repository.ExistsAsync(o => o.Id == 4))
                await _repository.AddAsync(new Book { Id = 4, Title = "TestBook" });

            _ = await _unitOfWork.Commit();

以上就是仓储模式+工作单元的简单实例。

事实上,EF 的设计就是工作单元+仓储模式

DbContext 作为工作单元,而DbContext中的DbSet则为一个仓储。

因此在现在的EF Core是否还需要建立仓储+工作单元模式,是一个值得思考的问题。

在微软的文档中这样说:

---------------------------------------------

使用自定义存储库与直接使用 EF DbContext

实体框架 DbContext 类基于工作单元和存储库模式,且可直接通过代码(如 ASP.NET Core MVC 控制器)进行使用。 工作单元模式和存储库模式产生最简单的代码,如 eShopOnContainers 的 CRUD 目录微服务中所示。 如果需要尽可能简单的代码,建议直接使用 DbContext 类,就像许多开发人员操作的那样。

但是,实现更复杂的微服务或应用程序时,实现自定义存储库将提供以下几个优势。 工作单元和存储库模式旨在封装基础结构持久性层,以便从应用程序和域模型层脱耦。 实现这些模式将促进用于模拟数据库访问的模拟数据库的使用。

图 7-18 中可以看到不使用存储库(直接使用 EF DbContext)与使用存储库(更易于模拟这些存储库)之间的差异。

显示两个存储库中的组件和数据流的关系图。

图 7-18。 使用自定义存储库与纯 DbContext

图 7-18 显示了使用自定义存储库添加了抽象层,可用于通过模拟存储库来简化测试。 模拟时有多个备选项。 只模拟存储库或模拟整个工作单元。 通常情况下,只模拟存储库就足够了,不需要提取并模拟整个工作单元那般的复杂。

稍后,当我们关注应用程序层时,将看到依赖关系注入在 ASP.NET Core 中的工作方式,以及使用存储库时的实现方式。

简而言之,自定义存储库允许使用不受数据层状态影响的单元测试来更轻松地测试代码。 如果运行的测试还通过 Entity Framework 访问实际的数据库,它们不是单元测试而是更为缓慢的集成测试。

如果直接使用 DbContext,则必须模拟它,或通过使用包含单元测试的可预测数据的内存中 SQL Server 来运行单元测试。 但模拟 DbContext 或控制假数据所需完成的工作比在存储库级别进行模拟所需完成的工作多。 当然,可以始终测试 MVC 控制器。

--------------------另外------------

https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-mvc/advanced?view=aspnetcore-3.1

 

 

因此,微软并不建议开发者再使用仓储模式+工作单元包裹EF的DbContext。

除非有更换ORM的必要,否则没必要将EF作为底层进行屏蔽。 

--------------------------------------

源码地址:

https://github.com/yeqifeng2288/RepositoryAndUnitOfWork

posted @ 2020-06-03 17:13  指左转右  阅读(1619)  评论(1编辑  收藏  举报