Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 為現任微軟最有價值專家 (MVP)、微軟認證講師 (MCT) 、Blogger、Youtuber:記錄軟體開發的點點滴滴 著重於微軟技術、C#、ASP .NET、Azure、DevOps、Docker、AI、Chatbot、Data Science

[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定義

/// <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都用的到的功能,也就是所謂的顯示給客戶端的功能,要如何打造一個容易傳遞資訊給顯示端,並且要修改顯示樣式的時候還很簡單。


留待下回分解。


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
台灣 Pay QR Code
台灣 Pay QR Code
Line Pay 一卡通 QR Code
Line Pay 一卡通 QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus