.net core2.x - 關於工作單元(UnitOfWork) 模式
概要:在搭建框架,順手說下寫下,關於unitofwork,可能你理解了,可能你還不理解,可能與不可能不是重點,重點是感興趣就看看吧。
1.工作單元(unitofowork)是什麼(後面簡寫uow)?
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
Unit of Work --Martin Fowler
uow是這個老兄(馬丁)提出的,上面是他說的話,這裡要注意的就是分兩個時間點,a)業務操作過程中,對操作的CUD操作的物件的狀態進行跟蹤操作; b) CUD操作完必經的一步驟當然是提交到資料庫,uow保證業務提交使用同意上下文物件,從而保證了事務的一致性,假設提交失敗直接回滾。
簡單說:uow可以說是一個開關,開:用於上下文的獲取,供所有的倉儲物件使用;關:提交操作,提交的過程包含事務控制(是否會滾)。
所以他這一句話我們可以明確這幾個東西:
①:一個物件用於存放 業務操作的物件(我們結合倉儲使用就是倉儲了) repository的臨時儲存物件;
②:同一業務操作的上下文必須保持一致(同一上下文物件)
3 :維護當前業務的變更操作(微軟自帶輸入法打不出來圓圈三,,,,)
④:事務控制
依據這幾點,所以我們可以和容易寫出我們想要的程式碼,(臨時先刨除 第三點 ,後面會有補充),先宣告:這裡未考慮多上下文的情況,因為我這邊用不到,但是實現也比較簡單,可以將涉及到的hashtable物件換成dictionary等,鍵為上下位物件型別或者名稱。
public class UnitOfWork : IUnitOfWork { #region fields /// <summary> /// 服務提供器,主要用於查詢 框架配置物件,以及DbContextOptionBuilder物件 /// </summary> private readonly IServiceProvider _provider; /// <summary> /// 當前請求涉及的scope生命的倉儲物件View Code/// </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 }
介面定義:
/// <summary> /// 工作單元介面 /// </summary> public interface IUnitOfWork : IDisposable { IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>; void BeginTransaction(); int Commit(); Task<int> CommitAsync(); }View Code
2.怎麼用?
就目前而言,部落格園中可見到的大部分的 實現都是將uow注入到 repository,通過 uow獲取上下文物件,然後在 service中只是直接注入所需的 repository物件,是的,這的確滿足我們的開發需求了,也能正常執行。
public class TestService { private readonly ITestRepository _testRepository; public TestService(ITestRepository testRepository){ _testRepository = testRepository; } //......其他方法實現 }
如果有多個倉儲物件,依次如上面的方式注入即可。但是,這樣做的話,當以後我們有表刪除或者新增的時候,我們不得不維護這樣的列表。這完全不符合OO設計原則;如果我們有新表的建立或者刪除,改動就比較多了。
如果你有細細觀察的話,我們這裡的 UOW實現稍有不同,也就涉及到當前請求的 倉儲物件(repository),我們在這零時儲存到了一個 hashable物件中,那麼這時候我們在 service中使用uow和倉儲的時候,就不用像上面那樣,有多少個需要註冊多少次,而只需要注入我們的一個uow物件即可。然後通過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層中注入一個uow即可,不論表如何變動,刪除或者新增表,我們這裡不會收到任何影響。比較理想的一種方式。
3.注意點?
uow模式注意點,也就是uow的說明 即:
1.由uow初始化上下文物件,也就是我們程式碼中的DbContext,;
2.由uow提供事務的控制方法,以及控制事務回滾,保證最終一致性
3.這裡我們還使用了uow進行倉儲物件的 獲取。
4.其他
4.補充:物件操作狀態的控制?
上面有說到,uow還需要對操作狀態的控制?啥意思?簡單說就是,一系列的 增、刪、改的 命令操作 的狀態控制,這裡的實現,園子已經在很早之前就有比較完善的實現了:
http://www.cnblogs.com/zxj159/p/3505457.html
基本原理就是 類似我們定義的 hashtable物件,定義三個 Dictionary 變數,用於儲存當前 業務操作涉及的 增、刪、改、三種操作的 儲存變數。
完。