倉儲模式+工作單元
倉儲模式
倉儲(Repository)模式自2004年首次作為領域驅動模型DDD設計的一部分引入,倉儲本質上是提供提供數據的抽象,以便應用程序可以使用具有接口的相似的簡單抽象集合。從此集合中CURD是通過一些列直接的方法完成,無需處理連接、命令等問題,使用此種模式可幫助實現松耦合,並保持領域對象的持久性無知。
- 倉儲模式是為了在程序的數據訪問層和業務邏輯層之間創建的一個抽象層
- 倉儲模式是一種數據訪問模式,提供一種更松散耦合的數據訪問方法
- 將創建數據訪問的邏輯寫在單獨的類中即倉儲
- 倉儲負責和業務層進行持久化通信
倉儲(Repository)是存在於工作單元和數據庫之間單獨分離出來的一層,是對數據訪問的封裝。其優點是
- 業務層無需知道具體實現達到分離關注點
- 提高對數據庫訪問的維護,對於倉儲的改變並不改變業務的邏輯。
如何處理多個Repository庫?
下面想象下如下場景,我們數據庫中有多個表,那樣我們需要為每個表創建一個Reporsitory類。(好多重復工作的說,其實這不是問題)
為什么每個Repository要擁有一個數據上下文的實例呢?為什么不在一些地方創建一個它的實例,然后在repository被實例化的時候作為參數傳遞進去呢。現在這個新的類被命名為 UnitOfWork ,此類將負責創建數據上下文實例並移交到控制器的所有repository實例。
對於倉儲Repository需要說明兩個方面的內容。一個是解決持久化的問題,一個是對數據層做屏蔽,避免應用或展現層直接跳過領域層對數據庫進行操作而使領域模型最終無用。在有了Repository后,我們不再關心對象的存儲和訪問操作,而將重心真正轉移到領域模型本身。或者叫使應用程序和領域設計與持久化技術解耦。
對於工廠和倉儲的關系,工廠負責對象生命周期的開始,而倉儲負責對象生命周期的中間或結束。當對象駐留在內存或對象數據庫的時候很好理解。但是至少有一部分數據會持久化存在到類似關系型數據庫或文件中,這樣檢索出來的數據就必須重建為對象形式。
對於工廠和倉儲的協同,有些理解和書上有些不一致。個人理解工廠不僅僅應該關注復雜對象的創建,同時也應該關注復雜對象的保存。工廠不負責對象的持久化,工廠將持久化職責委托到倉儲來完成。倉儲不應該直接和應用層打交道,對於整個領域層來說。和應用層打交道的是Service接口,而和持久化層打交道的是Repository接口而已。和書里面理解最大的差異就是Repository沒有保留給Client,也不是Repository委托Factory來重建對象。
舉一個場景來說,根據訂單號獲取一個聚合復雜對象采購訂單,對采購訂單進行修改后再進行保存。這個時候和持久化層存在兩次交互,第一次是數據的讀取,第二次是修改后數據的存入。
對於數據讀取,到領域層則是Factory需要實例化一個聚合對象並返回應用層。而Factory將該工作分解到聚合里美的每一個子實體,子實體通過Repository接口獲取到ResultSet並進行OR轉換后返回,Factory將拿到的所有實例化對象進行聚合返回一個完整的聚合對象實例。對於數據存儲,仍然應該是Factory接管該操作,然后對數據進行分解后分別調用聚合中的每一個實體的倉儲接口本身的保存方法,對數據進行持久化,在Factory層進行完整的事務控制並返回結果。
工作單元
UnitOfoWork是這個老兄(馬丁)提出的,上面是他說的話,這里要注意的就是分兩個時間點
a)業務操作過程中,對操作的CUD操作的對象的狀態進行跟蹤操作;
b) CUD操作完必經的一步驟當然是提交到數據庫,UnitOfoWor保證業務提交使用同意上下文對象,從而保證了事務的一致性,假設提交失敗直接回滾。
簡單說:UnitOfoWor可以說是一個開關,開:用於上下文的獲取,供所有的倉儲對象使用;關:提交操作,提交的過程包含事務控制(是否會滾)。
所以他這一句話我們可以明確這幾個東西:
①:一個對象用於存放 業務操作的對象(我們結合倉儲使用就是倉儲了) repository的臨時存儲對象;
②:同一業務操作的上下文必須保持一致(同一上下文對象)
3 :維護當前業務的變更操作
④:事務控制
依據這幾點,所以我們可以和容易寫出我們想要的代碼,(臨時先刨除 第三點 ,后面會有補充),先聲明:這里未考慮多上下文的情況,因為我這邊用不到,但是實現也比較簡單,可以將涉及到的hashtable對象換成dictionary等,鍵為上下位對象類型或者名稱。
/// <summary> /// 工作單元接口 /// </summary> public interface IUnitOfWork : IDisposable { IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>; void BeginTransaction(); int Commit(); Task<int> CommitAsync(); }
實現

public class UnitOfWork : IUnitOfWork { #region fields /// <summary> /// 服務提供器,主要用於查找 框架配置對象,以及DbContextOptionBuilder對象 /// </summary> private readonly IServiceProvider _provider; /// <summary> /// 當前請求涉及的scope生命的倉儲對象 /// </summary> private Hashtable repositorys; private IDbContextTransaction _dbTransaction { get; set; } /// <summary> /// 上下文對象,UOW內部初始化上下文對象,供當前scope內的操作使用,保證同一上下文 /// </summary> public DbContext DbContext => GetDbContext(); #endregion #region ctor public UnitOfWork(IServiceProvider provider) { _provider = provider; } #endregion #region public public IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey> { if (repositorys == null) repositorys = new Hashtable(); var entityType = typeof(TEntity); if (!repositorys.ContainsKey(entityType.Name)) { var baseType = typeof(Repository<,>); var repositoryInstance = Activator.CreateInstance(baseType.MakeGenericType(entityType), DbContext); repositorys.Add(entityType.Name, repositoryInstance); } return (IRepository<TEntity, TKey>)repositorys[entityType.Name]; } public void BeginTransaction() { //DbContext.Database.UseTransaction(_dbTransaction);//如果多上下文,我們可是在其他上下文直接使用 UserTransaction使用已存在的事務 _dbTransaction = DbContext.Database.BeginTransaction(); } public int Commit() { int result = 0; try { result = DbContext.SaveChanges(); if (_dbTransaction != null) _dbTransaction.Commit(); } catch (Exception ex) { result = -1; CleanChanges(DbContext); _dbTransaction.Rollback(); throw new Exception($"Commit 異常:{ex.InnerException}/r{ ex.Message}"); } return result; } public async Task<int> CommitAsync() { int result = 0; try { result = await DbContext.SaveChangesAsync(); if (_dbTransaction != null) _dbTransaction.Commit(); } catch (Exception ex) { result = -1; CleanChanges(DbContext); _dbTransaction.Rollback(); throw new Exception($"Commit 異常:{ex.InnerException}/r{ ex.Message}"); } return await Task.FromResult(result); } #endregion #region private private DbContext GetDbContext() { var options = _provider.ESoftorOption(); IDbContextOptionsBuilderCreator builderCreator = _provider.GetServices<IDbContextOptionsBuilderCreator>() .FirstOrDefault(d => d.DatabaseType == options.ESoftorDbOption.DatabaseType); if (builderCreator == null) throw new Exception($"無法解析數據庫類型為:{options.ESoftorDbOption.DatabaseType}的{typeof(IDbContextOptionsBuilderCreator).Name}實例"); //DbContextOptionsBuilder var optionsBuilder = builderCreator.Create(options.ESoftorDbOption.ConnectString, null);//TODO null可以換成緩存中獲取connection對象,以便性能的提升 if (!(ActivatorUtilities.CreateInstance(_provider, options.ESoftorDbOption.DbContextType, optionsBuilder.Options) is DbContext dbContext)) throw new Exception($"上下文對象 “{options.ESoftorDbOption.DbContextType.AssemblyQualifiedName}” 實例化失敗,請確認配置文件已正確配置。 "); return dbContext; } /// <summary> /// 操作失敗,還原跟蹤狀態 /// </summary> /// <param name="context"></param> private static void CleanChanges(DbContext context) { var entries = context.ChangeTracker.Entries().ToArray(); for (int i = 0; i < entries.Length; i++) { entries[i].State = EntityState.Detached; } } #endregion #region override public void Dispose() { _dbTransaction.Dispose(); DbContext.Dispose(); GC.SuppressFinalize(this); } #endregion }
就目前而言,博客園中可見到的大部分的 實現都是將UnitOfoWork注入到 repository,通過 UnitOfoWork獲取上下文對象,然后在 service中只是直接注入所需的 repository對象,是的,這的確滿足我們的開發需求了,也能正常運行。
public class TestService { private readonly ITestRepository _testRepository; public TestService(ITestRepository testRepository){ _testRepository = testRepository; } //......其他方法實現 }
如果有多個倉儲對象,依次如上面的方式注入即可。但是,這樣做的話,當以后我們有表刪除或者新增的時候,我們不得不維護這樣的列表。這完全不符合OO設計原則;如果我們有新表的創建或者刪除,改動就比較多了。
如果你有細細觀察的話,我們這里的 UnitOfoWork實現稍有不同,也就涉及到當前請求的 倉儲對象(repository),我們在這零時存儲到了一個 hashable對象中,那么這時候我們在 service中使用uow和倉儲的時候,就不用像上面那樣,有多少個需要注冊多少次,而只需要注入我們的一個UnitOfoWork對象即可。然后通過uow獲取 倉儲(repository)對象,因為我們零時將涉及到當前請求(事務)的 倉儲已經存儲到私有變量的 hashtable中,
public class TestService { private readonly IUnitOfWork _uow; public TestService(IUnitOfWork uow){ _uow = uow; } //......其他方法實現 }
然后我們在使用倉儲(repository)的時候,只需要如下方式使用即可:
var userRepository = _uow.Repository<User,Guid>(); var roleRepository = _uow.Repository<Role,Guid>(); ... 而在我們用到事務的地方,直接使用uow中的commit提交即可: _uow.BeginTransaction(); var userRepository = _uow.Repository<User,Guid>(); var roleRepository = _uow.Repository<Role,Guid>(); ...//一些列其他操作,(CRUD) _uow.Commit();
就像上面說到的,這樣保證了當前業務操作涉及的 倉儲對象(repository),會保證在 hashtable對象中,同時使用同一個上線問對象(DbContext),Commit提交的時候保證了事務(上下文)的一致性。而且如上面提到的,我們只需要在service層中注入一個UnitOfoWork即可,不論表如何變動,刪除或者新增表,我們這里不會收到任何影響。比較理想的一種方式。
UnitOfoWork模式注意點,也就是UnitOfoWork的說明 即:
1.由UnitOfoWork初始化上下文對象,也就是我們代碼中的DbContext,;
2.由UnitOfoWork提供事務的控制方法,以及控制事務回滾,保證最終一致性
3.這里我們還使用了UnitOfoWork進行倉儲對象的 獲取。
4.其他
UnitOfoWork還需要對操作狀態的控制 簡單說就是,一系列的 增、刪、改的 命令操作 的狀態控制 基本原理就是 類似我們定義的 hashtable對象,定義三個 Dictionary 變量,用於存儲當前 業務操作涉及的 增、刪、改、三種操作的 存儲變量
https://www.it1352.com/2064843.html 使用ReadCommitted隔離級別的事務