【.Net设计模式系列】工作单元(Unit Of Work)模式 ( 二 )
回顾
在上一篇博客【.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