在上一篇介紹完了Repository Pattern,我們能夠抽離實際在做儲存的動作,讓我們在替換實際儲存動作更加容易。
但是光靠一個Repository Pattern其實還是有些缺陷,因此,通常來說會實作Unit of Work pattern搭配Repository pattern達到一個比較完美的狀態。
Repository Pattern的問題是什麽
Repository Pattern代表一個儲存,以DB的世界來說,Repository 其實代表的是一個Table。在比較不複雜的程式來說,Repository層級可能就够了,但是如果要到一個複雜一點的程式,Repository pattern就有點相形見拙。
Repository Pattern最大的問題在於,當需要和一個以上的Table溝通的時候,DB的那種Atomic operation就沒有了。因為,Repository是針對Table,所以假設需要同時儲存到兩個Table,可以使用兩個Repository來做。問題在於,這兩個Repository彼此不知道對方,表示,假設Repository 1 儲存完成了,但是Repository 2 儲存失敗了,以完整的Atomic operation來說,只要一個失敗,整個operation應該算是失敗了,但是,因為Repository 之間是沒有聯繫的,因此資料會處於一種dirty state,就是一個進去了,但是一個失敗了。
要解決這個問題,我們就需有一個東西,來管理Repository彼此之間的情況,好讓它可以再一個成功另外一個失敗的情況下,整個roll back處理,而Unit of Work正是一個這樣的Pattern。
什麽是Unit of Work
基本上我們可以把一次的operation想做是一個unit of work。這個operation裡面可能有很多動作,或許需要更新3個table的資料,或許要新增3個table的資料。
這一個operation肯定是所有的動作都完成了,才算是整個operation結束。以DB的角度來想,就是像Transaction一樣的概念。
而Unit of Work這個pattern就是會對這個operation的每一個動作,做一個記錄。直到當被告知完成的時候,它才會真的去做處理,並且只有兩種情況回報:成功,或者失敗。
以DB的世界來說,Unit of Work代表一個DB,而Repository代表一個Table。
Entity Framework的DbContext
本身就有做Unit of Work。因此我們才能夠做一些CRUD,然後在一次呼叫SaveChange()
,而也是這個時候DbContext
才會真的把他有記錄的內容一次對DB做,並且返回成功或失敗。
Unit of Work的interface定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /// <summary> /// 實作Unit Of Work的interface。 /// </summary> public interface IUnitOfWork : IDisposable { /// <summary> /// 儲存所有異動。 /// </summary> void Save(); /// <summary> /// 取得某一個Entity的Repository。 /// 如果沒有取過,會initialise一個 /// 如果有就取得之前initialise的那個。 /// </summary> /// <typeparam name="T">此Context裡面的Entity Type</typeparam> /// <returns>Entity的Repository</returns> IRepository<T> Repository<T>() where T : class ; } |
基本上我們這邊需要實作的方法很簡單,一個是如何取得我們的Repository
。再來一個就是把所有透過Repository
的動作,透過save
存入到實體的位置。
Unit of Work的EF 實作
因為EF的DbContext
本身就有Unit of Work,因此我們實作起來非常簡單。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | /// <summary> /// 實作Entity Framework Unit Of Work的class /// </summary> public class EFUnitOfWork : IUnitOfWork { private readonly DbContext _context; private bool _disposed; private Hashtable _repositories; /// <summary> /// 設定此Unit of work(UOF)的Context。 /// </summary> /// <param name="context">設定UOF的context</param> public EFUnitOfWork(DbContext context) { _context = context; } /// <summary> /// 儲存所有異動。 /// </summary> public void Save() { _context.SaveChanges(); } /// <summary> /// 清除此Class的資源。 /// </summary> public void Dispose() { Dispose( true ); GC.SuppressFinalize( this ); } /// <summary> /// 清除此Class的資源。 /// </summary> /// <param name="disposing">是否在清理中?</param> protected virtual void Dispose( bool disposing) { if (!_disposed) { if (disposing) { _context.Dispose(); } } _disposed = true ; } /// <summary> /// 取得某一個Entity的Repository。 /// 如果沒有取過,會initialise一個 /// 如果有就取得之前initialise的那個。 /// </summary> /// <typeparam name="T">此Context裡面的Entity Type</typeparam> /// <returns>Entity的Repository</returns> public IRepository<T> Repository<T>() where T : class { if (_repositories == null ) { _repositories = new Hashtable(); } var type = typeof (T).Name; if (!_repositories.ContainsKey(type)) { var repositoryType = typeof (EFGenericRepository<>); var repositoryInstance = Activator.CreateInstance(repositoryType .MakeGenericType( typeof (T)), _context); _repositories.Add(type, repositoryInstance); } return (IRepository<T>)_repositories[type]; } } |
這邊沒有什麼太過於特別的東西,這個實作需要DbContext
,而這個DbContext
會透過Reflection注入到Repository裡面,因此所有的異動在 DbContext
都有記錄,因此,我們做Save
的時候,是一個Atomic的transaction。
結語
到了這邊,我們的Unit of Work和Repository就介紹完了。有了這兩個pattern的合作,DAL層的實作就可以完全達到抽象化,避免被綁死在某一個儲存技術。
在下一篇,我們來看一個比較簡單,但是是每一種Application都用的到的功能,也就是所謂的顯示給客戶端的功能,要如何打造一個容易傳遞資訊給顯示端,並且要修改顯示樣式的時候還很簡單。
留待下回分解。