2014年10月2日 星期四

[iThome 第七屆鐵人賽 10] 加上 Unit of Work,抽離Entity Framework的依賴就完美了

在上一篇介紹完了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有興趣,可以看一下這個Code Project的文章: Unit of Work Design Pattern

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的取得就沒有使用DI。當然,用不用DI見仁見智。

結語


到了這邊,我們的Unit of Work和Repository就介紹完了。有了這兩個pattern的合作,DAL層的實作就可以完全達到抽象化,避免被綁死在某一個儲存技術。


在下一篇,我們來看一個比較簡單,但是是每一種Application都用的到的功能,也就是所謂的顯示給客戶端的功能,要如何打造一個容易傳遞資訊給顯示端,並且要修改顯示樣式的時候還很簡單。


留待下回分解。