背景
考慮到目前中小企業應用的主流是ORM,我准備在NHibernate和EntityFramework之間找到一個抽象層,也就是說我准備只支持NHibernate和EntityFramework。
思路
NH和EF都實現了“工作單元”和“主鍵映射”這兩種企業應用模式,而這兩種模式其實就是管理一種狀態機,如下圖:
實現
工作單元接口
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Happy.Domain 8 { 9 /// <summary> 10 /// 工作單元接口。 11 /// </summary> 12 /// <remarks> 13 /// 聚合狀態: 14 /// <list type="number"> 15 /// <item>transient:實例存在於內存中,但不存在於工作單元和數據庫中。</item> 16 /// <item>persistent in database:實例存在於數據庫中。</item> 17 /// <item>persistent in unitofwork:實例存在於工作單元中。</item> 18 /// <item>detached:實例存在於內存和數據庫中,但不存在於工作單元中。</item> 19 /// </list> 20 /// 合法轉換: 21 /// <list type="number"> 22 /// <item>transient > Save -> persistent in unitofwork,Flush時會生成Insert Sql,場景:從UI層創建實例,執行創建。</item> 23 /// <item>detached -> Update -> persistent in unitofwork,Flush時會生成Update Sql,場景:從UI層重建實例,執行修改(支持離線樂觀並發)。</item> 24 /// <item>detached -> Persist -> persistent in unitofwork,Flush時不會生成Sql,場景:將實例從另一個工作單元脫鈎,添加到當前工作單元。</item> 25 /// <item>detached -> Delete -> persistent in unitofwork,Flush時會生成Delete Sql,場景:從UI層重建實例,刪除記錄。</item> 26 /// <item>detached -> Merge -> persistent in unitofwork,Flush時會生成Update Sql,場景:從UI層重建實例,合並到從數據庫重建的實例,執行修改(不支持離線樂觀並發)。</item> 27 /// <item>persistent in unitofwork -> Evict -> detached,Flush時不會生成Sql,場景:將實例從當前工作單元脫鈎,添加到另一個工作單元。</item> 28 /// <item>persistent in unitofwork -> Delete -> persistent in unitofwork,Flush時會生成Delete Sql,場景:從數據庫重建實例,刪除記錄。</item> 29 /// <item>persistent in unitofwork -> Flush -> persistent in database,提交工作單元,會生成SQL,場景:執行完一系列Create、Update和Delete后統一提交,只產生一次數據庫往返。</item> 30 /// <item>persistent in database -> Load -> persistent in unitofwork,從數據庫重建實例。</item> 31 /// <item>persistent in database -> Refresh -> persistent in unitofwork,從數據庫刷新實例,場景:使用存儲過程修改了一個實例,使用此方法重新刷新一下。</item> 32 /// </list> 33 /// </remarks> 34 public interface IUnitOfWork : IDisposable 35 { 36 /// <summary> 37 /// 判斷<paramref name="item"/>是否 persistent in unitofwork。 38 /// </summary> 39 bool Contains<TAggregateRoot>(TAggregateRoot item) 40 where TAggregateRoot : AggregateRoot; 41 42 /// <summary> 43 /// transient > Save -> persistent in unitofwork,Flush時會生成Insert Sql,場景:從UI層創建實例,執行創建。 44 /// </summary> 45 void Save<TAggregateRoot>(TAggregateRoot item) 46 where TAggregateRoot : AggregateRoot; 47 48 /// <summary> 49 /// detached -> Update -> persistent in unitofwork,Flush時會生成Update Sql,場景:從UI層重建實例,執行修改(支持離線樂觀並發)。 50 /// </summary> 51 void Update<TAggregateRoot>(TAggregateRoot item) 52 where TAggregateRoot : AggregateRoot; 53 54 /// <summary> 55 /// detached -> Persist -> persistent in unitofwork,Flush時不會生成Sql,場景:將實例從另一個工作單元脫鈎,添加到當前工作單元。 56 /// </summary> 57 void Persist<TAggregateRoot>(TAggregateRoot item) 58 where TAggregateRoot : AggregateRoot; 59 60 /// <summary> 61 /// 執行如下兩種轉換: 62 /// <list type="number"> 63 /// <item>detached -> Delete -> persistent in unitofwork,Flush時會生成Delete Sql,場景:從UI層重建實例,刪除記錄。</item> 64 /// <item>persistent in unitofwork -> Delete -> persistent in unitofwork,Flush時會生成Delete Sql,場景:從數據庫重建實例,刪除記錄。</item> 65 /// </list> 66 /// </summary> 67 void Delete<TAggregateRoot>(TAggregateRoot item) 68 where TAggregateRoot : AggregateRoot; 69 70 /// <summary> 71 /// detached -> Merge -> persistent in unitofwork,Flush時會生成Update Sql,場景:從UI層重建實例,合並到從數據庫重建的實例,執行修改(不支持離線樂觀並發)。 72 /// </summary> 73 void Merge<TAggregateRoot>(TAggregateRoot item) 74 where TAggregateRoot : AggregateRoot; 75 76 /// <summary> 77 /// persistent in unitofwork -> Evict -> detached,Flush時不會生成Sql,場景:將實例從當前工作單元脫鈎,添加到另一個工作單元。 78 /// </summary> 79 void Evict<TAggregateRoot>(TAggregateRoot item) 80 where TAggregateRoot : AggregateRoot; 81 82 /// <summary> 83 /// persistent in unitofwork -> Flush -> persistent in database,提交工作單元,會生成SQL,場景:執行完一系列Create、Update和Delete后統一提交,只產生一次數據庫往返。 84 /// </summary> 85 void Flush(); 86 87 /// <summary> 88 /// persistent in database -> Load -> persistent in unitofwork,從數據庫重建實例。 89 /// </summary> 90 TAggregateRoot Load<TAggregateRoot>(Guid id) 91 where TAggregateRoot : AggregateRoot; 92 93 /// <summary> 94 /// persistent in database -> Refresh -> persistent in unitofwork,從數據庫刷新實例,場景:使用存儲過程修改了一個實例,使用此方法重新刷新一下。 95 /// </summary> 96 void Refresh<TAggregateRoot>(TAggregateRoot item) 97 where TAggregateRoot : AggregateRoot; 98 99 /// <summary> 100 /// 回滾所有自上次提交以后的修改。 101 /// </summary> 102 void Clear(); 103 104 /// <summary> 105 /// 清空處於persistent in unitofwork狀態的實例。 106 /// </summary> 107 TRepository GetRepository<TRepository>() 108 where TRepository : IRepository; 109 } 110 }
基於EntityFramework的工作單元
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Data; 7 using System.Data.Entity; 8 using System.Data.Entity.Infrastructure; 9 10 using Microsoft.Practices.ServiceLocation; 11 12 using Happy.Domain; 13 using Happy.DesignByContract; 14 15 namespace Happy.EntityFramework 16 { 17 /// <summary> 18 /// 基於EntityFramework的工作單元。 19 /// </summary> 20 public abstract class UnitOfWork : DbContext, IUnitOfWork 21 { 22 private readonly Dictionary<Type, IRepository> repositories = new Dictionary<Type, IRepository>(); 23 24 /// <summary> 25 /// 構造方法。 26 /// </summary> 27 protected UnitOfWork() 28 { 29 } 30 31 /// <summary> 32 /// 構造方法。 33 /// </summary> 34 protected UnitOfWork(string nameOrConnectionString) 35 : base(nameOrConnectionString) 36 { 37 } 38 39 /// <inheritdoc /> 40 public bool Contains<TAggregateRoot>(TAggregateRoot item) 41 where TAggregateRoot : AggregateRoot 42 { 43 item.MustNotNull("item"); 44 45 return this.Entry(item).State != EntityState.Detached; 46 } 47 48 /// <inheritdoc /> 49 public void Save<TAggregateRoot>(TAggregateRoot item) 50 where TAggregateRoot : AggregateRoot 51 { 52 item.MustNotNull("item"); 53 54 this.Set<TAggregateRoot>().Add(item); 55 } 56 57 /// <inheritdoc /> 58 public void Update<TAggregateRoot>(TAggregateRoot item) 59 where TAggregateRoot : AggregateRoot 60 { 61 item.MustNotNull("item"); 62 63 this.Entry(item).State = EntityState.Modified; 64 } 65 66 /// <inheritdoc /> 67 public void Persist<TAggregateRoot>(TAggregateRoot item) 68 where TAggregateRoot : AggregateRoot 69 { 70 item.MustNotNull("item"); 71 72 this.Entry(item).State = EntityState.Unchanged; 73 } 74 75 /// <inheritdoc /> 76 public void Delete<TAggregateRoot>(TAggregateRoot item) 77 where TAggregateRoot : AggregateRoot 78 { 79 item.MustNotNull("item"); 80 81 this.Entry(item).State = EntityState.Deleted; 82 } 83 84 /// <inheritdoc /> 85 public void Merge<TAggregateRoot>(TAggregateRoot item) 86 where TAggregateRoot : AggregateRoot 87 { 88 item.MustNotNull("item"); 89 90 var persistItem = this.Set<TAggregateRoot>().Find(item.Id); 91 92 this.Entry(persistItem).CurrentValues.SetValues(item); 93 } 94 95 /// <inheritdoc /> 96 public void Evict<TAggregateRoot>(TAggregateRoot item) 97 where TAggregateRoot : AggregateRoot 98 { 99 item.MustNotNull("item"); 100 101 this.Entry(item).State = EntityState.Detached; 102 } 103 104 /// <inheritdoc /> 105 public void Flush() 106 { 107 try 108 { 109 base.SaveChanges(); 110 } 111 catch (DbUpdateConcurrencyException ex) 112 { 113 throw new OptimisticConcurrencyException(ex.Message, ex); 114 } 115 } 116 117 public TAggregateRoot Load<TAggregateRoot>(Guid id) 118 where TAggregateRoot : AggregateRoot 119 { 120 return this.Set<TAggregateRoot>().Find(id); 121 } 122 123 public void Refresh<TAggregateRoot>(TAggregateRoot item) 124 where TAggregateRoot : AggregateRoot 125 { 126 item.MustNotNull("item"); 127 128 this.Entry(item).Reload(); 129 } 130 131 /// <inheritdoc /> 132 public void Clear() 133 { 134 base.ChangeTracker.Entries() 135 .ToList() 136 .ForEach(entry => entry.State = System.Data.EntityState.Detached); 137 } 138 139 /// <inheritdoc /> 140 public TRepository GetRepository<TRepository>() 141 where TRepository : IRepository 142 { 143 var key = typeof(TRepository); 144 145 if (!repositories.ContainsKey(key)) 146 { 147 var repository = ServiceLocator.Current.GetInstance<TRepository>(); 148 (repository as IEntityFrameworkRepository).Owner = this; 149 repositories[key] = repository; 150 } 151 152 return (TRepository)repositories[key]; 153 } 154 } 155 }
備注
其實我們經常忽略一個關於接口的問題,就是異常本身也是API的一部分,雖然這部分在C#中沒有辦法顯式的表達,等我的朋友實現完了NH版本的工作單元的開發,我們就繼續對異常進行抽象。