UnitOfWork+Repository模式簡介:
每次提交數據庫都會打開一個連接,造成結果是:多個連接無法共用一個數據庫級別的事務,也就無法保證數據的原子性、一致性。解決辦法是:在Repository的CRUD操作基礎上再包裝一層,提供統一的入口,讓服務層調用。同一個UnitOfWork實例對象下所有的Repository都共同一個數據庫上下文對象(ps:EF用的是DbContext),也就是共用一個事物。提交數據庫時,只要有一個操作失敗,那么所有的操作都被視為失敗。
項目結構:
關鍵代碼:
AggregateRoot.cs:
1 using System; 2 using System.Collections.Generic; 3 4 namespace CMS.Domain.Core 5 { 6 /// <summary> 7 /// 表示聚合根類型的基類型。 8 /// </summary> 9 public abstract class AggregateRoot : IAggregateRoot 10 { 11 #region 方法 12 13 public virtual IEnumerable<BusinessRule> Validate() 14 { 15 return new BusinessRule[] { }; 16 } 17 18 #endregion 19 20 #region Object 成員 21 22 public override bool Equals(object obj) 23 { 24 if (obj == null) 25 return false; 26 27 if (ReferenceEquals(this, obj)) 28 return true; 29 30 IAggregateRoot ar = obj as IAggregateRoot; 31 32 if (ar == null) 33 return false; 34 35 return this.Id == ar.Id; 36 } 37 38 public override int GetHashCode() 39 { 40 return this.Id.GetHashCode(); 41 } 42 43 #endregion 44 45 #region IAggregateRoot 成員 46 47 public Guid Id 48 { 49 get; 50 set; 51 } 52 53 #endregion 54 } 55 }
Channel.cs:
1 using CMS.Domain.Core; 2 3 namespace CMS.Domain.Entities 4 { 5 public class Channel : AggregateRoot 6 { 7 public string Name 8 { 9 get; 10 set; 11 } 12 13 public string CoverPicture 14 { 15 get; 16 set; 17 } 18 19 public string Desc 20 { 21 get; 22 set; 23 } 24 25 public bool IsActive 26 { 27 get; 28 set; 29 } 30 31 public int Hits 32 { 33 get; 34 set; 35 } 36 } 37 }
IUnitOfWork.cs:
1 using System; 2 3 namespace CMS.Domain.Core.Repository 4 { 5 /// <summary> 6 /// 工作單元 7 /// 提供一個保存方法,它可以對調用層公開,為了減少連庫次數 8 /// </summary> 9 public interface IUnitOfWork : IDisposable 10 { 11 #region 方法 12 13 IRepository<T> Repository<T>() where T : class, IAggregateRoot; 14 15 void Commit(); 16 17 #endregion 18 } 19 }
UnitOfWork.cs:
1 using CMS.Common; 2 using CMS.Domain.Core; 3 using CMS.Domain.Core.Repository; 4 using System; 5 using System.Collections; 6 using System.Collections.Generic; 7 8 namespace CMS.Infrastructure 9 { 10 public class UnitOfWork : IUnitOfWork, IDisposable 11 { 12 #region 變量 13 14 private bool _disposed; 15 private readonly IDbContext _dbContext; 16 private Hashtable _repositories; 17 18 #endregion 19 20 #region 構造函數 21 22 public UnitOfWork(IDbContext dbContext) 23 { 24 this._dbContext = dbContext; 25 this._repositories = new Hashtable(); 26 } 27 28 #endregion 29 30 #region 方法 31 32 public virtual void Dispose(bool disposing) 33 { 34 if (!this._disposed) 35 if (disposing) 36 this._dbContext.Dispose(); 37 38 this._disposed = true; 39 } 40 41 #endregion 42 43 #region IUnitOfWork 成員 44 45 public IRepository<T> Repository<T>() where T : class, IAggregateRoot 46 { 47 var typeName = typeof(T).Name; 48 49 if (!this._repositories.ContainsKey(typeName)) 50 { 51 52 53 var paramDict = new Dictionary<string, object>(); 54 paramDict.Add("context", this._dbContext); 55 56 //Repository接口的實現統一在UnitOfWork中執行,通過Unity來實現IOC,同時把IDbContext的實現通過構造函數參數的方式傳入 57 var repositoryInstance = UnityConfig.Resolve<IRepository<T>>(paramDict); 58 59 if (repositoryInstance != null) 60 this._repositories.Add(typeName, repositoryInstance); 61 } 62 63 return (IRepository<T>)this._repositories[typeName]; 64 } 65 66 public void Commit() 67 { 68 this._dbContext.SaveChanges(); 69 } 70 71 #endregion 72 73 #region IDisposable 成員 74 75 public void Dispose() 76 { 77 this.Dispose(true); 78 79 GC.SuppressFinalize(this); 80 } 81 82 #endregion 83 } 84 }
BaseRepository.cs:
1 using CMS.Domain.Core; 2 using CMS.Domain.Core.Repository; 3 using System; 4 using System.Collections.Generic; 5 using System.Data.Entity; 6 using System.Linq; 7 using System.Linq.Expressions; 8 9 namespace CMS.Infrastructure 10 { 11 public class BaseRepository<T> : IRepository<T> where T : class, IAggregateRoot 12 { 13 #region 變量 14 15 private readonly DbContext _db; 16 private readonly IDbSet<T> _dbset; 17 18 #endregion 19 20 #region 構造函數 21 22 public BaseRepository(IDbContext context) 23 { 24 this._db = (DbContext)context; 25 this._dbset = this._db.Set<T>(); 26 } 27 28 #endregion 29 30 #region IRepository 成員 31 32 public void Add(T item) 33 { 34 this._dbset.Add(item); 35 } 36 37 public void Remove(T item) 38 { 39 this._dbset.Remove(item); 40 } 41 42 public void Modify(T item) 43 { 44 this._db.Entry(item).State = EntityState.Modified; 45 } 46 47 public T Get(Expression<Func<T, bool>> filter) 48 { 49 return this._dbset.Where(filter).SingleOrDefault(); 50 } 51 52 public IEnumerable<T> GetAll() 53 { 54 return this._dbset.ToList(); 55 } 56 57 public IEnumerable<T> GetPaged<KProperty>(int pageIndex, int pageSize, out int total, Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderBy, bool ascending = true, string[] includes = null) 58 { 59 pageIndex = pageIndex > 0 ? pageIndex : 1; 60 61 var result = this.GetFiltered(filter, orderBy, ascending, includes); 62 63 total = result.Count(); 64 65 return result.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToList(); 66 } 67 68 public IEnumerable<T> GetFiltered<KProperty>(Expression<Func<T, bool>> filter, Expression<Func<T, KProperty>> orderBy, bool ascending = true, string[] includes = null) 69 { 70 var result = filter == null ? this._dbset : this._dbset.Where(filter); 71 72 if (ascending) 73 result = result.OrderBy(orderBy); 74 else 75 result = result.OrderByDescending(orderBy); 76 77 if (includes != null && includes.Length > 0) 78 { 79 foreach (var include in includes) 80 { 81 result = result.Include(include); 82 } 83 } 84 85 return result.ToList(); 86 } 87 88 #endregion 89 } 90 }
IDbContext.cs:
1 namespace CMS.Infrastructure 2 { 3 public interface IDbContext 4 { 5 #region 方法 6 7 int SaveChanges(); 8 9 void Dispose(); 10 11 #endregion 12 } 13 }
CMSDbContext.cs:
1 using CMS.Infrastructures.Mapping; 2 using System.Data.Entity; 3 using System.Data.Entity.ModelConfiguration.Conventions; 4 5 namespace CMS.Infrastructure 6 { 7 public class CMSDbContext : DbContext, IDbContext 8 { 9 #region 構造函數 10 11 public CMSDbContext() 12 : base("SqlConnectionString") 13 { 14 15 } 16 17 #endregion 18 19 #region DbContext 重寫 20 21 protected override void OnModelCreating(DbModelBuilder modelBuilder) 22 { 23 modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 24 25 modelBuilder.Configurations.Add(new ChannelEntityConfiguration()); 26 } 27 28 #endregion 29 } 30 }
UnityConfig.cs:
1 using Microsoft.Practices.Unity; 2 using Microsoft.Practices.Unity.Configuration; 3 using System; 4 using System.Collections.Generic; 5 6 namespace CMS.Common 7 { 8 public class UnityConfig 9 { 10 #region 屬性 11 12 public static IUnityContainer Container 13 { 14 get 15 { 16 return container.Value; 17 } 18 } 19 20 #endregion 21 22 #region 方法 23 24 private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>( 25 () => 26 { 27 var container = new UnityContainer(); 28 29 RegisterTypes(container); 30 31 return container; 32 }); 33 34 private static void RegisterTypes(IUnityContainer container) 35 { 36 container.LoadConfiguration(); 37 } 38 39 public static T Resolve<T>(IDictionary<string, object> paramDict = null) 40 { 41 var list = new ParameterOverrides(); 42 43 if (paramDict != null && paramDict.Count > 0) 44 { 45 foreach (var item in paramDict) 46 { 47 list.Add(item.Key, item.Value); 48 } 49 } 50 51 return Container.Resolve<T>(list); 52 } 53 54 #endregion 55 } 56 }
ChannelApplcationService.cs:
1 using AutoMapper; 2 using CMS.Domain.Core.Repository; 3 using CMS.Domain.Entities; 4 using CMS.DTO; 5 using Microsoft.Practices.Unity; 6 7 namespace CMS.Applcation 8 { 9 public class ChannelApplcationService 10 { 11 #region 屬性 12 13 [Dependency] 14 public IUnitOfWork UnitOfWork { get; set; } 15 16 #endregion 17 18 #region 方法 19 20 public Response<bool> Add(ChannelDTO dto) 21 { 22 var resp = new Response<bool>(); 23 var channel = Mapper.Map<Channel>(dto); 24 25 using (this.UnitOfWork) 26 { 27 var channelAddRepository = this.UnitOfWork.Repository<Channel>(); 28 29 channelAddRepository.Add(channel); 30 31 this.UnitOfWork.Commit(); 32 } 33 34 resp.Result = true; 35 36 return resp; 37 } 38 39 #endregion 40 } 41 }
序列圖:
心得體會:
1. Repository的CRUD操作只能作用於繼承了AggregateRoot基類的DomainObject(ps:以實際項目情況為准,可以做適當的妥協)。
2. DomainObject中涉及到集合類型(如IList,ISet等)的聚合屬性需要加“virtual”關鍵字,讓ORM框架識別做Lazyload處理。
3. 各自獨立的業務邏輯寫在對應的DomainObject方法中,方法體內只能處理自身以及內部聚合對象的數據和狀態等信息,被聚合的對象不建議里面再有方法,只需定義相關屬性即可(ps:涉及到對外通知、發布消息等場景以DomainEvent的方式處理,關於DomainEvent的概念和使用會開新章進行簡述)。
4. 把需要多個DomainObject交互和協調的業務邏輯放到DomainService中(ps:在Applcation Layer中調用。另外DomainService是否能調用Repository對象我一直很困惑,因為看過有代碼是這么寫的,但又有人不建議這么做......)。
5. 在AggregateRoot基類中定義驗證BusinessRule的虛方法,供子類重寫,並在統一的地方執行(比如Applcation Layer)
6. 定義DomainException,用來封裝Domain Layer層的異常信息,對上層(Applcation Layer)暴露。
7. Applcation Layer代碼的主要作用(可用WCF、WebAPI或直接Dll引用等方式對上層(UI Layer)暴露)
- 接收UI Layer傳遞的DTO對象。
- 通過AutoMapper組件轉換成對應的DomainObject,並調用其方法(ps:內部業務邏輯的封裝)。
- 調用Repository對象來實現CRUD操作(ps:這時數據還只是在內存中)。
- 調用UnitOfWork的Commit方法來實現數據的真正提交(ps:事物級別的)。
所以可以看出Applcation Layer主要用來處理業務的執行順序,而不是關鍵的業務邏輯。
Applcation Layer如果用WCF或WebAPI的方式對外暴露有個好處,可以針對其作負載均衡,壞處是額外增加了IIS的請求開銷。
8. DTO和DomainObject區別
DTO(ps:為了簡單起見,這里把DTO和ViewModel放在一塊說了):
- 根據實際業務場景加上Required、StringLength等驗證特性,結合MVC框架的內部驗證機制,可在Controller層做到數據的有效性驗證(ps:永遠都不要輕易相信瀏覽器端提交的數據,即使已經有了js腳本驗證......)。
- 負責View數據的展現和表單提交時數據的封裝。
- 負責把數據從UI Layer傳遞到Applcation Layer,里面只能有屬性,而且是扁平的,結構簡單的屬性。
DomainObject:通俗點說就是充血模型,包括屬性和行為,在DDD整個框架設計體系中占非常重要的地位,其涵蓋了整個軟件系統的業務邏輯、業務規則、聚合關系等方面。(ps;如果業務很簡單,可以只有屬性)
9. UI Layer:自我學習UnitOfWork+Repository以來,一直用的是MVC框架做前台展現,選擇的理由:1. Unity4MVC的IOC功能非常強大,2. 天生支持AOP的思想,3. 更加傳統、原始的Web處理方式,4. Areas模塊對插件化設計的支持,5. Ajax、ModelBuilder、JSON、驗證,6. 整個Http訪問周期內提供的各種擴展點等。