倉儲模式:
倉儲模式源自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作為底層進行屏蔽。
--------------------------------------
源碼地址:
