文章出處

本來早就準備總結一下關于Repository、IUnitOfWork之間的聯系以及在各層中的分布,直到看到田園里的蟋蟀發表的文章:《DDD 領域驅動設計-談談 Repository、IUnitOfWork 和 IDbContext 的實踐》,才覺得有必要發表一下我個人的觀點及其相關的實現代碼,當然我的觀點不一定就比他們的好,我只是表達個人觀點而矣,大家勿噴。

關于Repository可以看看DUDU的這篇文章:關于Repository模式,我結合實際應用總結其核心概念為:Repository是受領域驅動及基于領域的意圖對外(領域服務、領域實體、應用服務層)提供管理實體的服務,本身不對數據的持久化負責,也不應該出現未受領域約束的方法。

關于Unit Of Work,我認為它的作用是:管理數據持久化的問題,并不受外界影響(比如:并發)確保在同一個工作單元(或者說是同一個業務領域)下操作的一致性(即:要么都成功,要么就都失敗),類似事務;

Entity Framework的DbContext其實就實現了Unit Of Work的功能(比如:SET用來查詢數據,同時通過SET的Add及Remove向DbContext注冊增加及刪除的請求服務,對于更新則是通過自動適時追蹤來實現的,只有通過SaveChanges才將實體的狀態執行化到數據庫中),于是關于在使用Entity Framework后有沒有必要再實現Unit Of Work,博客園的大牛們都有過爭論,我覺得應該依項目的實際情況來定,如果項目簡單且不考慮更換其它數據庫以及單元測試的便利性,那么直接使用DbContext就OK了,但如果不是,那么就需要用到Unit Of Work+Repository來包裝隔離實際的持久化實現。

對于Repository、IUnitOfWork 在領域層和應用服務層之間的關聯與代碼分布,我采用如下圖方式:

下面就來分享我關于Repository、IUnitOfWork 實現代碼:

Exam.Domain.IUnitOfWork定義:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Exam.Domain
{
    public interface IUnitOfWork
    {
        IQueryable<TAggregateRoot> Entities<TAggregateRoot>() 
            where TAggregateRoot : class, IAggregateRoot;

        void RegisterNew<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot;

        void RegisterModified<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot;

        void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot;

        //void RegisterClean();

        void Commit();
    }
}

我這里將RegisterClean注釋掉原因是:我認為一旦注冊了相應的持久化請求,那說明實體的狀態已經被更改了,而此時你執行清除沒有任何意義,有人可能會說,不清除在執行提交時會持久化到數據庫中,我想說,你不提交就行了,因為UnitOfWork是一個工作單元,它的影響范圍應僅限在這個工作單元內,當然想法很美好,現實有點殘酷,所以為了應對可能出現的反悔的問題,我這里還是寫出來了只是注釋掉了,具體怎樣,我們接著往下看。

 Exam.Domain.Repositories.IRepository定義:

    public interface IRepository<TAggregateRoot> where TAggregateRoot:class,IAggregateRoot
    {
        void Add(TAggregateRoot entity);

        void Update(TAggregateRoot entity);

        void Delete(TAggregateRoot entity);

        TAggregateRoot Get(Guid id);

        IQueryable<TResult> Find<TResult>(Expression<Func<TAggregateRoot, bool>> whereExpr, Expression<Func<TAggregateRoot, TResult>> selectExpr);

    }

我這里定義的IRepository包括基本的查、增、改、刪,有人可能又會說,你不是說倉儲中不應對包括持久化嗎?注意這里只里的增、刪、改只是用來向UnitOfWork發出相應的持久化請求的。當然也可以去掉倉儲中的這些方法,僅保留查詢方法,而若需要持久化就去調用UnitOfWork的相應的方法,正如 田園里的蟋蟀 那篇博文實現的那樣,但我覺得UnitOfWork工作單元不應該去主動要求持久化,而是應該被動的接收倉儲的持久化請求。

 

 Exam.Repositories.IDbContext定義:

    public interface IDbContext
    {
        DbSet<TEntity> Set<TEntity>()
            where TEntity : class;

        DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)
            where TEntity : class;

        Task<int> SaveChangesAsync();
    }

Exam.Repositories.EfUnitOfWork定義:

 public class EfUnitOfWork:IUnitOfWork
    {
        private readonly IDbContext context;

        public EfUnitOfWork(IDbContext context) //如果要啟用RegisterClean,則IDbContext必需還要繼承自IObjectContextAdapter
        {
            this.context = context;
        }

        public IQueryable<TAggregateRoot> Entities<TAggregateRoot>()
      where TAggregateRoot : class, IAggregateRoot
        {
            return context.Set<TAggregateRoot>();
        }

        public void RegisterNew<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot
        {
            context.Set<TAggregateRoot>().Add(entity);
        }

        public void RegisterModified<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot
        {
            context.Entry(entity).State = EntityState.Modified;
        }

        public void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot
        {
            context.Entry(entity).State = EntityState.Deleted;
        }

        //public void RegisterClean()
        //{
        //    var objectContext = ((IObjectContextAdapter)context).ObjectContext;
        //    List<ObjectStateEntry> entries = new List<ObjectStateEntry>();
        //    var states = new[] { EntityState.Added, EntityState.Deleted, EntityState.Modified};
        //    foreach (var state in states)
        //    {
        //        entries.AddRange(objectContext.ObjectStateManager.GetObjectStateEntries(state));
        //    }

        //    foreach (var item in entries)
        //    {
        //        objectContext.ObjectStateManager.ChangeObjectState(item.Entity, EntityState.Unchanged);
        //        //objectContext.Detach(item.Entity);可直接用這句替換上句
        //    }
        //}

         async public void Commit()
        {
            await context.SaveChangesAsync();
        }
    }

這里的RegisterClean依然注釋掉了,當然如果啟用,則IDbContext必需還要繼承自IObjectContextAdapter,因為清除方法中用到了它,我這里的清除是真正的清除所有上下文中緩存。即便這樣在某種情況下仍存在問題,比如:Repository向UnitOfWork注冊了相應的操作后,沒有執行清除操作,也沒有提交,就這樣又在其它的業務領域中用到了相關的實體并且操作還不一樣,這時就會出現問題,我能想到的解決辦法如下:

    public class EfUnitOfWork2:IUnitOfWork
    {
        private readonly IDbContext context;

        private readonly Dictionary<Type,IAggregateRoot> registedNews;
        private readonly Dictionary<Type, IAggregateRoot> registedModified;
        private readonly Dictionary<Type, IAggregateRoot> registedDeleted;

        private void Register<TAggregateRoot>(Dictionary<Type, IAggregateRoot> registerContainer, TAggregateRoot entity) where TAggregateRoot : class, IAggregateRoot
        {
            if (registerContainer.Values.Count(t=>t.Id==entity.Id)<=0)
            {
                registerContainer.Add(typeof(TAggregateRoot), entity);
            }
        }

        public EfUnitOfWork2(IDbContext context) //如果要啟用RegisterClean,則IDbContext必需還要繼承自IObjectContextAdapter
        {
            this.context = context;
            registedNews = new Dictionary<Type, IAggregateRoot>();
            registedModified = new Dictionary<Type, IAggregateRoot>();
            registedDeleted = new Dictionary<Type, IAggregateRoot>();
        }

        public IQueryable<TAggregateRoot> Entities<TAggregateRoot>()
      where TAggregateRoot : class, IAggregateRoot
        {
            return context.Set<TAggregateRoot>();
        }

        public void RegisterNew<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot
        {
            Register(registedNews, entity);
        }

        public void RegisterModified<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot
        {
            Register(registedModified, entity);
        }

        public void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity)
            where TAggregateRoot : class, IAggregateRoot
        {
            Register(registedDeleted, entity);
        }

         async public void Commit()
        {
            foreach (var t in registedNews.Keys)
            {
                context.Set(t).Add(registedNews[t]);
            }

            foreach (var t in registedModified.Keys)
            {
                context.Entry(registedModified[t]).State = EntityState.Modified;
            }

            foreach (var t in registedDeleted.Keys)
            {
                context.Entry(registedDeleted[t]).State = EntityState.Deleted;
            }
            await context.SaveChangesAsync();
        }
    }

注意這里用到了DbContext中的DbSet Set(Type entityType)方法,所以IDbContext需加上該方法定義就可以了,這樣上面說的問題就解決了。其實與這篇實現的方法類似:

http://www.cnblogs.com/GaoHuhu/p/3443145.html

Exam.Repositories.Repository的定義:

    public abstract class Repository<TAggregateRoot> : IRepository<TAggregateRoot>
         where TAggregateRoot : class,IAggregateRoot
    {
        private readonly IUnitOfWork unitOfWork;

        public Repository(IUnitOfWork uow)
        {
            unitOfWork = uow;
        }

        public void Add(TAggregateRoot entity)
        {
            unitOfWork.RegisterNew(entity);
        }

        public void Update(TAggregateRoot entity)
        {
            unitOfWork.RegisterModified(entity);
        }

        public void Delete(TAggregateRoot entity)
        {
            unitOfWork.RegisterDeleted(entity);
        }

        public TAggregateRoot Get(Guid id)
        {
            return  unitOfWork.Entities<TAggregateRoot>().FirstOrDefault(t => t.Id == id);
        }

        public IQueryable<TResult> Find<TResult>(Expression<Func<TAggregateRoot, bool>> whereExpr, Expression<Func<TAggregateRoot, TResult>> selectExpr)
        {
            return unitOfWork.Entities<TAggregateRoot>().Where(whereExpr).Select(selectExpr);
        }
    }

這是一個通用的Repository抽象類,其它所有的倉儲在繼承該類的基礎上實現它自己的方法,目的是為了減輕重復代碼,順便看一下,我定義的接口中相關的持久化操作均用到了TAggregateRoot,表示聚合根,所以的操作均應以聚合根來進行,這里DDD里面的約束,我剛開始也有些不解,但仔細一想,是有道理的,我們舉個例子說明一下:

訂單與訂單項,訂單應為聚合根,訂單項應為實體或值對象,為什么這么說呢?

1.先有訂單存在,才會有訂單項;

2.訂單項不允許單獨自行刪除,若要刪除需通過訂單來執行,一般要么訂單創建,要么訂單刪除,不存在訂單生成后,還要去刪除訂單項的,比如:京東的訂單,你去看看生成訂單后,還能否在不改變訂單的情況下刪除訂單中的某個物品的。

3.訂單查詢出來了,相應的訂單項也就知道了,不存在只知道訂單項,而不知道訂單的情況。

描述的可能還不夠準確,但綜上所述基本可以確定聚合關系,而且若使用了EF,它的自動跟蹤與延遲加載特性也會為實現聚合根帶來方便,當然了也可以自行實現類似EF的自動跟蹤與延遲加載功能,已經有人實現了類似功能,可以看netfocus相關文章。

下面是演示示例:

            //Unity IOC容器,我這里是演示直接寫代碼,其實更好的建議是通過配置注冊類型映射
            var container = new UnityContainer();
            container.RegisterType<IDbContext, ExamDbConext>(new ContainerControlledLifetimeManager());
            container.RegisterType<IUnitOfWork, EfUnitOfWork>();
            //container.RegisterType<IOrderRepository, OrderRepository>();

            var unitofWork = container.Resolve<IUnitOfWork>();
            //var orderRepository = container.Resolve<IOrderRepository>();
            var orderRepository = new OrderRepository(unitofWork);

            //增加
            orderRepository.Add(new Order()
            {
                OrderNo = "SO20151016",
                CreateDatetime = DateTime.Now,
                Status = "New",
                OrderItems = new[] { 
                    new OrderItem(){ ProductName="CPU", Description="CPU規格描述"},
                    new OrderItem(){ ProductName="HDD", Description="HDD規格描述"},
                    new OrderItem(){ ProductName="MB", Description="MB規格描述"},
                    new OrderItem(){ ProductName="KB", Description="KB規格描述"},
                }
            });
            unitofWork.Commit();

            //更改
            var order=orderRepository.Find(t => true, t => t).First();
            order.OrderItems.Clear(); //清除所有子項
            orderRepository.Update(order);//其實利用EF自動跟蹤狀態,如果在EF上下文中可以不用調用這句
            unitofWork.Commit();


            //刪除
            orderRepository.Delete(order);
            unitofWork.Commit();

  


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()