在上一篇介紹完了Repository Pattern,我們能夠抽離實際在做儲存的動作,讓我們在替換實際儲存動作更加容易。
但是光靠一個Repository Pattern其實還是有些缺陷,因此,通常來說會實作Unit of Work pattern搭配Repository pattern達到一個比較完美的狀態。
同步發表於我的部落格:http://alantsai2007.blogspot.tw/2014/10/BuildYourOwnApplicationFrameworkOnMvc-10-UnitOfWork.html
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有興趣,可以看一下這個Code Project的文章: Unit of Work Design Pattern
Unit of Work的interface定義
/// <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,因此我們實作起來非常簡單。
/// <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的取得就沒有使用DI。當然,用不用DI見仁見智。
結語
到了這邊,我們的Unit of Work和Repository就介紹完了。有了這兩個pattern的合作,DAL層的實作就可以完全達到抽象化,避免被綁死在某一個儲存技術。
在下一篇,我們來看一個比較簡單,但是是每一種Application都用的到的功能,也就是所謂的顯示給客戶端的功能,要如何打造一個容易傳遞資訊給顯示端,並且要修改顯示樣式的時候還很簡單。
留待下回分解。