回顧
在上一篇博客【.Net設計模式系列】倉儲(Repository)模式 ( 一 ) 中,通過各位兄台的評論中,可以看出在設計上還有很多的問題,在這里特別感謝 @橫豎都溢 @ 浮雲飛夢 2位兄台對博文中存在的問題給予指出,並提供出好的解決方案,同時也感謝其他園友的支持。歡迎各位園友對博文中出現的錯誤或者是設計誤區給予指出,一方面防止“誤人子弟”,另一方面則可以讓大家共同成長。
對於上一篇博客,只是給大家提供了一種對於小型項目數據訪問層的一種實現方式,通過Sql語句和傳遞參數來實現CRUD。並未達到真正意義上的解耦。特此在本篇繼續完善。
理論介紹
在進行數據庫添加、修改、刪除時,為了保證事務的一致性,即操作要么全部成功,要么全部失敗。例如銀行A、B兩個賬戶的轉賬業務。一方失敗都會導致事務的不完整性,從而事務回滾。而工作單元模式可以跟蹤事務,在操作完成時對事務進行統一提交。
理論參考:http://martinfowler.com/eaaCatalog/unitOfWork.html
具體實踐
首先,講解下設計思想:領域層通過相應的庫實現泛型倉儲接口來持久化聚合類,之后在抽象庫中將對泛型倉儲接口提供基礎實現,並將對應的實體轉化為SQl語句。這樣領域層就不需要操作Sql語句即可完成CRUD操作,同時使用工作單元對事務進行統一提交。
1)定義倉儲接口,包含基本的CRUD操作及其重載不同的查詢
1 public interface IRepository<T> 2 { 3 /// <summary> 4 /// 插入對象 5 /// </summary> 6 /// <param name="entity"></param> 7 int Insert(T entity); 8 9 /// <summary> 10 /// 更新對象 11 /// </summary> 12 /// <param name="entity"></param> 13 /// <param name="predicate"></param> 14 int Update(T entity, Expression<Func<T, bool>> express); 15 16 /// <summary> 17 /// 刪除對象 18 /// </summary> 19 /// <param name="predicate"></param> 20 int Delete(Expression<Func<T, bool>> express = null); 21 22 /// <summary> 23 /// 查詢對象集合 24 /// </summary> 25 /// <param name="predicate"></param> 26 /// <returns></returns> 27 List<T> QueryAll(Expression<Func<T, bool>> express = null); 28 29 /// <summary> 30 /// 查詢對象集合 31 /// </summary> 32 /// <param name="index"></param> 33 /// <param name="pagesize"></param> 34 /// <param name="order"></param> 35 /// <param name="asc"></param> 36 /// <param name="express"></param> 37 /// <returns></returns> 38 List<T> QueryAll(int index,int pagesize,List<PropertySortCondition> orderFields, Expression<Func<T, bool>> express = null); 39 40 /// <summary> 41 /// 查詢對象集合 42 /// </summary> 43 /// <param name="type"></param> 44 /// <param name="predicate"></param> 45 /// <returns></returns> 46 List<object> QueryAll(Type type, Expression<Func<T, bool>> express = null); 47 48 /// <summary> 49 /// 查詢對象 50 /// </summary> 51 /// <param name="predicate"></param> 52 /// <returns></returns> 53 T Query(Expression<Func<T, bool>> express); 54 55 /// <summary> 56 /// 查詢數量 57 /// </summary> 58 /// <param name="predicate"></param> 59 /// <returns></returns> 60 object QueryCount(Expression<Func<T, bool>> express = null); 61 }
其次,對倉儲接口提供基本實現,這里由於使用了Lambda表達式,所以就需要進行表達式樹的解析(這里我希望園友能自己去研究)。
1 public abstract class BaseRepository<T> : IRepository<T> 2 where T:class,new() 3 { 4 private IUnitOfWork unitOfWork; 5 6 private IUnitOfWorkContext context; 7 8 public BaseRepository(IUnitOfWork unitOfWork, IUnitOfWorkContext context) 9 { 10 this.unitOfWork = unitOfWork; 11 this.context = context; 12 } 13 14 Lazy<ConditionBuilder> builder = new Lazy<ConditionBuilder>(); 15 16 public string tableName { 17 get 18 { 19 TableNameAttribute attr= (TableNameAttribute)typeof(T).GetCustomAttribute(typeof(TableNameAttribute)); 20 return attr.Name; 21 } 22 } 23 24 /// <summary> 25 /// 插入對象 26 /// </summary> 27 /// <param name="entity"></param> 28 public virtual int Insert(T entity) 29 { 30 Func<PropertyInfo[], string, IDictionary<string, object>, int> excute = (propertys, condition, parameters) => 31 { 32 List<string> names = new List<string>(); 33 foreach (PropertyInfo property in propertys) 34 { 35 if (property.GetCustomAttribute(typeof(IncrementAttribute)) == null) 36 { 37 string attrName = property.Name; 38 object value = property.GetValue(entity); 39 names.Add(string.Format("@{0}", attrName)); 40 parameters.Add(attrName, value); 41 } 42 } 43 string sql = "Insert into {0} values({1})"; 44 string combineSql = string.Format(sql, tableName, string.Join(",", names), builder.Value.Condition); 45 return unitOfWork.Command(combineSql, parameters); 46 }; 47 return CreateExcute<int>(null, excute); 48 } 49 50 /// <summary> 51 /// 修改對象 52 /// </summary> 53 /// <param name="entity"></param> 54 /// <param name="express"></param> 55 public virtual int Update(T entity, Expression<Func<T, bool>> express) 56 { 57 58 Func<PropertyInfo[], string, IDictionary<string, object>, int> excute = (propertys, condition, parameters) => 59 { 60 List<string> names = new List<string>(); 61 foreach (PropertyInfo property in propertys) 62 { 63 if (property.GetCustomAttribute(typeof(IncrementAttribute)) == null) 64 { 65 string attrName = property.Name; 66 object value = property.GetValue(entity); 67 names.Add(string.Format("{0}=@{1}", attrName, attrName)); 68 parameters.Add(attrName, value); 69 } 70 } 71 string sql = "update {0} set {1} where {2}"; 72 string combineSql = string.Format(sql, tableName, string.Join(",", names), builder.Value.Condition); 73 return unitOfWork.Command(combineSql, parameters); 74 }; 75 return CreateExcute<int>(express, excute); 76 } 77 /// <summary> 78 /// 刪除對象 79 /// </summary> 80 /// <param name="express"></param> 81 public virtual int Delete(Expression<Func<T, bool>> express = null) 82 { 83 Func<PropertyInfo[], string, IDictionary<string, object>, int> excute = (propertys, condition, parameters) => 84 { 85 string sql = "delete from {0} {1}"; 86 string combineSql = string.Format(sql, tableName, condition); 87 return unitOfWork.Command(combineSql, parameters); 88 }; 89 return CreateExcute<int>(express, excute); 90 } 91 92 /// <summary> 93 /// 查詢對象集合 94 /// </summary> 95 /// <param name="express"></param> 96 /// <returns></returns> 97 public virtual List<T> QueryAll(Expression<Func<T, bool>> express = null) 98 { 99 Func<PropertyInfo[], string, IDictionary<string, object>, List<T>> excute = (propertys, condition, parameters) => 100 { 101 string sql = "select {0} from {1} {2}"; 102 string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition); 103 return context.ReadValues<T>(combineSql, parameters); 104 }; 105 return CreateExcute<List<T>>(express, excute); 106 } 107 108 /// <summary> 109 /// 查詢對象集合(分頁) 110 /// </summary> 111 /// <param name="index"></param> 112 /// <param name="pagesize"></param> 113 /// <param name="order"></param> 114 /// <param name="asc"></param> 115 /// <param name="express"></param> 116 /// <returns></returns> 117 public virtual List<T> QueryAll(int index,int pagesize,List<PropertySortCondition> orderFields,Expression<Func<T, bool>> express = null) 118 { 119 Func<PropertyInfo[], string, IDictionary<string, object>, List<T>> excute = (propertys, condition, parameters) => 120 { 121 if (orderFields == null) { throw new Exception("排序字段不能為空"); } 122 string sql = "select * from (select {0} , ROW_NUMBER() over(order by {1}) as rownum from {2} {3}) as t where t.rownum >= {4} and t.rownum < {5}"; 123 string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)),string.Join(",", orderFields), tableName, condition, (index - 1) * pagesize + 1, index * pagesize); 124 return context.ReadValues<T>(combineSql, parameters); 125 }; 126 return CreateExcute<List<T>>(express, excute); 127 } 128 129 /// <summary> 130 /// 查詢對象集合 131 /// </summary> 132 /// <param name="type"></param> 133 /// <param name="express"></param> 134 /// <returns></returns> 135 public virtual List<object> QueryAll(Type type, Expression<Func<T, bool>> express = null) 136 { 137 Func<PropertyInfo[], string, IDictionary<string, object>, List<object>> excute = (propertys, condition, parameters) => 138 { 139 string sql = "select {0} from {1} {2}"; 140 string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition); 141 return context.ReadValues(combineSql, type, parameters); 142 }; 143 return CreateExcute<List<object>>(express, excute); 144 } 145 146 /// <summary> 147 /// 查詢對象 148 /// </summary> 149 /// <param name="express"></param> 150 /// <returns></returns> 151 public virtual T Query(Expression<Func<T, bool>> express) 152 { 153 Func<PropertyInfo[], string, IDictionary<string, object>, T> excute = (propertys, condition, parameters) => 154 { 155 string sql = "select {0} from {1} {2}"; 156 string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition); 157 return context.ExecuteReader<T>(combineSql, parameters); 158 }; 159 return CreateExcute<T>(express, excute); 160 } 161 162 /// <summary> 163 /// 查詢數量 164 /// </summary> 165 /// <param name="express"></param> 166 /// <returns></returns> 167 public virtual object QueryCount(Expression<Func<T, bool>> express = null) 168 { 169 Func<PropertyInfo[], string, IDictionary<string, object>, object> excute = (propertys, condition, parameters) => 170 { 171 string sql = "select * from {0} {1}"; 172 string combineSql = string.Format(sql, string.Join(",", propertys.Select(x => x.Name)), tableName, condition); 173 return context.ExecuteScalar(combineSql, parameters); 174 }; 175 176 return CreateExcute<object>(express, excute); 177 } 178 179 private TValue CreateExcute<TValue>(Expression<Func<T, bool>> express, Func<PropertyInfo[], string, IDictionary<string, object>, TValue> excute) 180 { 181 Dictionary<string, object> parameters = new Dictionary<string, object>(); 182 PropertyInfo[] propertys = typeof(T).GetProperties(); 183 string condition = ""; 184 if (express != null) 185 { 186 builder.Value.Build(express, tableName); 187 condition = string.Format("where {0} ", builder.Value.Condition); 188 for (int i = 0; i < builder.Value.Arguments.Length; i++) 189 { 190 parameters.Add(string.Format("Param{0}", i), builder.Value.Arguments[i]); 191 } 192 } 193 return excute(propertys, condition, parameters); 194 } 195 }
接下來,定義工作單元,所有的添加、刪除、修改操作都會被儲存到工作單元中。
1 public interface IUnitOfWork 2 { 3 /// <summary> 4 /// 命令 5 /// </summary> 6 /// <param name="commandText"></param> 7 /// <param name="parameters"></param> 8 /// <returns></returns> 9 int Command(string commandText, IDictionary<string, object> parameters); 10 11 /// <summary> 12 /// 事務的提交狀態 13 /// </summary> 14 bool IsCommited { get; set; } 15 16 /// <summary> 17 /// 提交事務 18 /// </summary> 19 /// <returns></returns> 20 void Commit(); 21 22 /// <summary> 23 /// 回滾事務 24 /// </summary> 25 void RollBack(); 26 }
接下來是對工作單元的實現,其內部維護了一個命令集合,存儲Sql語句。
1 public class UnitOfWork:IUnitOfWork 2 { 3 /// <summary> 4 /// 注入對象 5 /// </summary> 6 private IUnitOfWorkContext context; 7 8 /// <summary> 9 /// 維護一個Sql語句的命令列表 10 /// </summary> 11 private List<CommandObject> commands; 12 13 public UnitOfWork(IUnitOfWorkContext context) 14 { 15 commands = new List<CommandObject>(); 16 this.context = context; 17 } 18 19 /// <summary> 20 /// 增、刪、改命令 21 /// </summary> 22 /// <param name="commandText"></param> 23 /// <param name="parameters"></param> 24 /// <returns></returns> 25 public int Command(string commandText, IDictionary<string, object> parameters) 26 { 27 IsCommited = false; 28 commands.Add(new CommandObject(commandText, parameters)); 29 return 1; 30 } 31 32 /// <summary> 33 /// 提交狀態 34 /// </summary> 35 public bool IsCommited{ get; set; } 36 37 /// <summary> 38 /// 提交方法 39 /// </summary> 40 /// <returns></returns> 41 public void Commit() 42 { 43 if (IsCommited) { return ; } 44 using (TransactionScope scope = new TransactionScope()) 45 { 46 foreach (var command in commands) 47 { 48 context.ExecuteNonQuery(command.command, command.parameters); 49 } 50 scope.Complete(); 51 IsCommited = true; 52 } 53 } 54 55 /// <summary> 56 /// 事務回滾 57 /// </summary> 58 public void RollBack() 59 { 60 IsCommited = false; 61 } 62 }
最后定義工作單元對事務提交處理的上下文及其實現,同倉儲模式中的代碼。
1 public interface IUnitOfWorkContext 2 { 3 4 /// <summary> 5 /// 注冊新對象到上下文 6 /// </summary> 7 /// <param name="commandText"></param> 8 /// <param name="parameters"></param> 9 int ExecuteNonQuery(string commandText, IDictionary<string, object> parameters = null); 10 11 /// <summary> 12 /// 查詢對象集合 13 /// </summary> 14 /// <typeparam name="T"></typeparam> 15 /// <param name="commandText"></param> 16 /// <param name="parameters"></param> 17 /// <param name="load">自定義處理</param> 18 /// <returns></returns> 19 List<T> ReadValues<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class, new(); 20 21 /// <summary> 22 /// 查詢對象集合 23 /// </summary> 24 /// <param name="commandText"></param> 25 /// <param name="type"></param> 26 /// <param name="parameters"></param> 27 /// <param name="setItem"></param> 28 /// <returns></returns> 29 List<object> ReadValues(string commandText, Type type, IDictionary<string, object> parameters = null, Action<dynamic> setItem = null); 30 31 /// <summary> 32 /// 查詢對象 33 /// </summary> 34 /// <typeparam name="TEntity"></typeparam> 35 /// <param name="commandText"></param> 36 /// <param name="parameters"></param> 37 /// <param name="excute"></param> 38 /// <returns></returns> 39 T ExecuteReader<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class,new(); 40 41 /// <summary> 42 /// 查詢數量 43 /// </summary> 44 /// <param name="commandText"></param> 45 /// <param name="parameters"></param> 46 /// <returns></returns> 47 object ExecuteScalar(string commandText, IDictionary<string, object> parameters = null); 48 }
最后實現。

1 public abstract class UnitOfWorkContext : IUnitOfWorkContext,IDisposable 2 { 3 /// <summary> 4 /// 數據庫連接字符串標識 5 /// </summary> 6 public abstract string Key { get; } 7 8 private SqlConnection connection; 9 10 private SqlConnection Connection 11 { 12 get 13 { 14 if (connection == null) 15 { 16 ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings[Key]; 17 connection = new SqlConnection(settings.ConnectionString); 18 } 19 return connection; 20 } 21 } 22 23 /// <summary> 24 /// 注冊新對象到事務 25 /// </summary> 26 /// <typeparam name="TEntity"></typeparam> 27 /// <param name="entity"></param> 28 public int ExecuteNonQuery(string commandText, IDictionary<string, object> parameters = null) 29 { 30 Func<SqlCommand, int> excute = (commend) => 31 { 32 return commend.ExecuteNonQuery(); 33 }; 34 return CreateDbCommondAndExcute<int>(commandText, parameters, excute); 35 } 36 37 /// <summary> 38 /// 查詢對象集合 39 /// </summary> 40 /// <typeparam name="T"></typeparam> 41 /// <param name="commandText"></param> 42 /// <param name="parameters"></param> 43 /// <param name="load">自定義處理</param> 44 /// <returns>泛型實體集合</returns> 45 46 public List<T> ReadValues<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class,new() 47 { 48 Func<SqlCommand, List<T>> excute = (dbCommand) => 49 { 50 List<T> result = new List<T>(); 51 using (IDataReader reader = dbCommand.ExecuteReader()) 52 { 53 while (reader.Read()) 54 { 55 if (load == null) 56 { 57 load = (s) => { return s.GetReaderData<T>(); }; 58 } 59 var item = load(reader); 60 result.Add(item); 61 } 62 return result; 63 } 64 }; 65 return CreateDbCommondAndExcute(commandText, parameters, excute); 66 } 67 68 /// <summary> 69 /// 查詢對象集合 70 /// </summary> 71 /// <param name="commandText"></param> 72 /// <param name="parameters"></param> 73 /// <param name="setItem"></param> 74 /// <returns></returns> 75 public List<object> ReadValues(string commandText, Type type, IDictionary<string, object> parameters = null, Action<dynamic> setItem = null) 76 { 77 Func<SqlCommand, List<object>> excute = (dbCommand) => 78 { 79 var result = new List<object>(); 80 81 using (IDataReader dataReader = dbCommand.ExecuteReader()) 82 { 83 while (dataReader.Read()) 84 { 85 var item = dataReader.GetReaderData(type); 86 if (setItem != null) 87 { 88 setItem(item); 89 } 90 result.Add(item); 91 } 92 } 93 return result; 94 }; 95 return CreateDbCommondAndExcute<List<object>>(commandText, parameters, 96 excute); 97 } 98 99 /// <summary> 100 /// 查詢對象 101 /// </summary> 102 /// <typeparam name="TEntity"></typeparam> 103 /// <param name="commandText"></param> 104 /// <param name="parameters"></param> 105 /// <param name="excute"></param> 106 /// <returns></returns> 107 public T ExecuteReader<T>(string commandText, IDictionary<string, object> parameters = null, Func<IDataReader, T> load = null) where T : class,new() 108 { 109 Func<SqlCommand, T> excute = (dbCommand) => 110 { 111 var result = default(T); 112 using (IDataReader reader = dbCommand.ExecuteReader()) 113 { 114 while (reader.Read()) 115 { 116 if (load == null) 117 { 118 load = (s) => { return s.GetReaderData<T>(); }; 119 } 120 result = load(reader); 121 } 122 return result; 123 } 124 }; 125 return CreateDbCommondAndExcute<T>(commandText, parameters, excute); 126 } 127 128 /// <summary> 129 /// 查詢數量 130 /// </summary> 131 /// <param name="commandText"></param> 132 /// <param name="parameters"></param> 133 /// <returns></returns> 134 public object ExecuteScalar(string commandText, IDictionary<string, object> parameters = null) 135 { 136 Func<SqlCommand, object> excute = (dbCommand) => 137 { 138 return dbCommand.ExecuteScalar(); 139 }; 140 return CreateDbCommondAndExcute(commandText, parameters, excute); 141 } 142 143 /// <summary> 144 /// 創建命令並執行 145 /// </summary> 146 /// <typeparam name="TValue"></typeparam> 147 /// <param name="commandText"></param> 148 /// <param name="parameters"></param> 149 /// <param name="excute"></param> 150 /// <returns></returns> 151 private TValue CreateDbCommondAndExcute<TValue>(string commandText, 152 IDictionary<string, object> parameters, Func<SqlCommand, TValue> excute) 153 { 154 if (Connection.State == ConnectionState.Closed) { Connection.Open(); }; 155 using (SqlCommand command = new SqlCommand()) 156 { 157 command.CommandType = CommandType.Text; 158 command.CommandText = commandText;; 159 command.Connection = Connection; 160 command.SetParameters(parameters); 161 return excute(command); 162 } 163 } 164 165 /// <summary> 166 /// 關閉連接 167 /// </summary> 168 public void Dispose() 169 { 170 if (connection != null) 171 { 172 Connection.Dispose();//非托管資源 173 } 174 } 175 }
在調用方法時需要注意,一個事務涉及多個聚合時,需要保證傳遞同一工作單元,並在方法的最后調用Commit() 方法。
1 public class Services : IService 2 { 3 private IMemberRespository member; 4 5 private IUnitOfWork unitOfWork; 6 7 public Services(IMemberRespository member, IUnitOfWork unitOfWork) 8 { 9 this.member = member; 10 this.unitOfWork = unitOfWork; 11 } 12 13 /// <summary> 14 /// 測試用例 15 /// </summary> 16 public void Demo() 17 { 18 19 member.Test(); 20 21 unitOfWork.Commit(); 22 } 23 }
后記
該實現中並未實現對多表進行的聯合查詢,使用Lambda的方式去多表查詢,有點自己寫一個ORM的性質,由於Lz能力有限,顧有需求的園友可以自行擴展或者使用ORM,若有實現自行擴展的園友,望指教。
至此,既實現對數據訪問層和領域層解耦,如果園友對我的比較認可,歡迎嘗試去使用,在使用中遇到什么問題或有什么好的意見,也希望及時反饋給我。若某些園友不太認可我的設計,也希望批評指出。
源碼網盤地址:鏈接:http://pan.baidu.com/s/1hqXJ3GK 密碼:o0he