来源: Orm框架开发之NewExpression合并问题 – 张家华 – 博客园
之前都是看别人写博客,自己没有写博客的习惯.在工作的过程中,总是会碰到许多的技术问题.有很多时候想记录下来,后面一直有许多的问题等着解决.总想着等系统完成了,再回头总结下.往往结果就把这事抛到脑后了.
总觉得不能一直这样哈.今天简单记一下吧.有表达不清楚的地方,多多包涵.
最近在研究.net orm框架.想开发一套更好用的Orm框架.碰到一个Expression合并的问题.别嫌轮子多.
一.需求情况描述
需要更新部分数据的时候,可能前端传回的只有部分字段的数据.而更新的时候,需要设置更新人,更新日期等.
举个栗子来说:
现在有一个预约信息表
前端需要修改数据内容如下,我们暂且叫表达A
var exp = ExpressionHelper.CreateExpression<AppointmentDto>(a => new {
a.Id,
a.PatientName,
a.PatientNamePy,
a.IdentityCardNumber,
a.Birthday,
a.PatientAge,
a.PatientSex,
a.PatientPhone,
a.Address
});
而写入数据库的时候需要添加更新人,更新时间.LastUpdateUserId和UpdateTime.
于是我们便又多了一个lambda表达式,我们叫它表达式B
var exp = ExpressionHelper.CreateExpression<AppointmentDto>(a => new {
a.Id,
a.PatientName,
a.PatientNamePy,
a.IdentityCardNumber,
a.Birthday,
a.PatientAge,
a.PatientSex,
a.PatientPhone,
a.Address,
a.LastUpdateUserId,
a.UpdateTime
});
这里说下ExpressionHelper.CreateExpression<T>方法,只是一个为了缩减代码长度而写的方法.输入的lambda表达式原样返回了.
外面不用写好长的类型了.Expression这个类型平时不用.写外面看着眼晕. Expression<Func<AppointmentDto, object>> exp1 = a => new {a.Id,a.PatientName};
/// <summary>
/// 转换Expr
/// 在外面调用时可以使用var以减少代码长度
/// </summary>
/// <param name="expr"></param>
/// <returns></returns>
public static Expression<Func<T, object>> CreateExpression<T>(Expression<Func<T, object>> expr)
{
return expr;
}
所以此处,使用var可以看起来更整洁.但并不推荐在正常情况下使用var.
个人觉得使用var让代码可维护性降低.读起来真的是头疼.之前在维护一个比较大的系统的时候,公司的主要项目,缺少项目文档,代码里面也基本上没啥注释.而且又清一色的var,每个方法返回的是啥类型?你得去方法那边看去.看着真是恼火,又不得不去一点一点的改.都改成相应的类型后,看着就清爽多了.看一眼,流程就基本上能明白大概.所以,var在C#这种强类型语言里,能不用就别用了.
上面就当是发牢骚了.我们回到正题.
我们看到表达式B比表达式A只多了两个字段.大多数代码都是重复的.而且,两个lambda表达式严重的加长了代码行数.几个这样的表达式下来,这个类就到了几百行了.
对于喜欢简洁,简单的我来说,类一大了我就头疼.那咋整?要是有办法将这两个表达式简化处理一下就好了.将表达式A加上一个短的表达式,来实现表达式B呢.
比如实现 var exprB = exprA.Add(a => new { a.PatientPhone });
So,开始捯饬…
二.解决方法
因为这个合并表达式的方法是在个人系统内部使用满足我定制的Orm的类名称需求
所以定义了一个新的Expression表达式类型NewObjectExpression来处理
1 /// <summary>
2 /// New Object Expression
3 /// 合并NewExpression使用.
4 /// </summary>
5 public class NewObjectExpression : Expression, IArgumentProvider
6 {
7 private IList<Expression> arguments;
8
9 /// <summary>
10 /// 构造方法
11 /// </summary>
12 /// <param name="constructor"></param>
13 /// <param name="arguments"></param>
14 /// <param name="members"></param>
15 internal NewObjectExpression(ConstructorInfo constructor, IList<Expression> arguments, List<MemberInfo> members)
16 {
17 this.Constructor = constructor;
18 this.arguments = arguments;
19 this.Members = members;
20
21 if (members != null)
22 {
23 List<string> nameList = members.Select(member => member.Name).ToList();
24 for (int i = 0; i < nameList.Count; i++)
25 {
26 if (!string.IsNullOrEmpty(ExpressionString))
27 {
28 ExpressionString += "," + nameList[i];
29 }
30 else
31 {
32 ExpressionString = nameList[i];
33 }
34 }
35 }
36 }
37
38 /// <summary>
39 /// Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression"/>.)
40 /// </summary>
41 /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
42 public override Type Type
43 {
44 get { return Constructor.DeclaringType; }
45 }
46
47 /// <summary>
48 /// Returns the node type of this <see cref="Expression" />. (Inherited from <see cref="Expression" />.)
49 /// </summary>
50 /// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns>
51 public sealed override ExpressionType NodeType
52 {
53 get { return ExpressionType.New; }
54 }
55
56 /// <summary>
57 /// Gets the called constructor.
58 /// </summary>
59 public ConstructorInfo Constructor { get; }
60
61 /// <summary>
62 /// Gets the arguments to the constructor.
63 /// </summary>
64 public ReadOnlyCollection<Expression> Arguments
65 {
66 get { return (ReadOnlyCollection<Expression>)arguments; }
67 }
68
69 Expression IArgumentProvider.GetArgument(int index)
70 {
71 return arguments[index];
72 }
73
74 int IArgumentProvider.ArgumentCount
75 {
76 get
77 {
78 return arguments.Count;
79 }
80 }
81
82 /// <summary>
83 /// ExpressionString
84 /// </summary>
85 public string ExpressionString { get; private set; } = "";
86
87 public ConstructorInfo Constructor1 => Constructor;
88
89 public List<MemberInfo> Members { get; set; }
90
91 /// <summary>
92 /// 更新members
93 /// </summary>
94 /// <param name="arguments"></param>
95 /// <param name="members"></param>
96 /// <returns></returns>
97 public NewObjectExpression Update(IList<Expression> arguments, List<MemberInfo> members)
98 {
99 if (arguments != null)
100 {
101 this.arguments = arguments;
102 }
103 if (Members != null)
104 {
105 this.Members = members;
106 ExpressionString = "";
107 List<string> nameList = members.Select(member => member.Name).ToList();
108 for (int i = 0; i < nameList.Count; i++)
109 {
110 if (!string.IsNullOrEmpty(ExpressionString))
111 {
112 ExpressionString += "," + nameList[i];
113 }
114 else
115 {
116 ExpressionString = nameList[i];
117 }
118 }
119 }
120 return this;
121 }
122 }
待处理的属性都放到了Members里面.后面解析使用的也是Members.其它方法Copy自NewExpression的源码,可以删了不用.
下面我们来扩展Expression<Func<T, object>>,让Expression<Func<T, object>>拥有Add和Remove属性的方法.
直接上代码,看前两个方法.后面两个方法是扩展Expression<Func<T, bool>>表达式的And和Or.等有回头有空再介绍.
1 /// <summary>
2 /// Expression 扩展
3 /// </summary>
4 public static class ExpressionExpand
5 {
6 /// <summary>
7 /// Expression And
8 /// NewExpression 合并
9 /// </summary>
10 /// <param name="expr"></param>
11 /// <returns></returns>
12 public static Expression<Func<T, object>> Add<T>(this Expression<Func<T, object>> expr, Expression<Func<T, object>> expandExpr)
13 {
14 Expression<Func<T, object>> result = null;
15 ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
16 List<MemberInfo> memberInfoList = new List<MemberInfo>();
17 #region 处理原expr
18 if (expr.Body is NewExpression)
19 { // t=>new{t.Id,t.Name}
20 NewExpression newExp = expr.Body as NewExpression;
21 if (newExp.Members != null)
22 {
23 memberInfoList = newExp.Members.ToList();
24 }
25 }
26 else if (expr.Body is NewObjectExpression)
27 {
28 NewObjectExpression newExp = expr.Body as NewObjectExpression;
29 if (newExp.Members != null)
30 {
31 memberInfoList = newExp.Members.ToList();
32 }
33 }
34 else if (expr.Body is UnaryExpression)
35 { //t=>t.Id
36 UnaryExpression unaryExpression = expr.Body as UnaryExpression;
37 MemberExpression memberExp = unaryExpression.Operand as MemberExpression;
38 memberInfoList.Add(memberExp.Member);
39 }
40 #endregion
41
42 #region 处理扩展expr
43 if (expandExpr.Body is NewExpression)
44 { // t=>new{t.Id,t.Name}
45 NewExpression newExp = expandExpr.Body as NewExpression;
46 for (int i = 0; i < newExp.Members.Count; i++)
47 {
48 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name);
49 if (!memberInfoList.Any(member => member.Name == newExp.Members[i].Name))
50 {
51 memberInfoList.Add(newExp.Members[i]);
52 }
53 }
54 }
55 else if (expr.Body is NewObjectExpression)
56 {
57 NewObjectExpression newExp = expr.Body as NewObjectExpression;
58 if (newExp.Members != null && newExp.Members.Count > 0)
59 {
60 for (int i = 0; i < newExp.Members.Count; i++)
61 {
62 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name);
63 if (!memberInfoList.Any(member => member.Name == newExp.Members[i].Name))
64 {
65 memberInfoList.Add(newExp.Members[i]);
66 }
67 }
68 }
69 }
70 else if (expandExpr.Body is UnaryExpression)
71 { //t=>t.Id
72 UnaryExpression unaryExpression = expandExpr.Body as UnaryExpression;
73 MemberExpression memberExp = unaryExpression.Operand as MemberExpression;
74 if (!memberInfoList.Any(exp => exp.Name == memberExp.Member.Name))
75 {
76 memberInfoList.Add(memberExp.Member);
77 }
78 }
79 #endregion
80 NewObjectExpression newObjExpression = new NewObjectExpression(typeof(object).GetConstructors()[0], null, memberInfoList);
81 result = Expression.Lambda<Func<T, object>>(newObjExpression, parameter);
82 return result;
83 }
84
85 /// <summary>
86 /// Expression Remove
87 /// NewExpression 合并
88 /// </summary>
89 /// <param name="expr"></param>
90 /// <returns></returns>
91 public static Expression<Func<T, object>> Remove<T>(this Expression<Func<T, object>> expr, Expression<Func<T, object>> expandExpr)
92 {
93 Expression<Func<T, object>> result = null;
94 ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
95 List<MemberInfo> memberInfoList = new List<MemberInfo>();
96 List<MemberInfo> removeMemberInfoList = new List<MemberInfo>();
97 #region 处理原expr
98 if (expr.Body is NewExpression)
99 { // t=>new{t.Id,t.Name}
100 NewExpression newExp = expr.Body as NewExpression;
101 if (newExp.Members != null)
102 {
103 memberInfoList = newExp.Members.ToList();
104 }
105 }
106 else if (expr.Body is NewObjectExpression)
107 {
108 NewObjectExpression newExp = expr.Body as NewObjectExpression;
109 if (newExp.Members != null)
110 {
111 memberInfoList = newExp.Members.ToList();
112 }
113 }
114 else if (expr.Body is UnaryExpression)
115 { //t=>t.Id
116 UnaryExpression unaryExpression = expr.Body as UnaryExpression;
117 MemberExpression memberExp = unaryExpression.Operand as MemberExpression;
118 memberInfoList.Add(memberExp.Member);
119 }
120 #endregion
121
122 #region 处理扩展expr
123 if (expandExpr.Body is NewExpression)
124 { // t=>new{t.Id,t.Name}
125 NewExpression newExp = expandExpr.Body as NewExpression;
126 for (int i = 0; i < newExp.Members.Count; i++)
127 {
128 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name);
129 if (!removeMemberInfoList.Any(member => member.Name == newExp.Members[i].Name))
130 {
131 removeMemberInfoList.Add(newExp.Members[i]);
132 }
133 }
134 }
135 else if (expr.Body is NewObjectExpression)
136 {
137 NewObjectExpression newExp = expr.Body as NewObjectExpression;
138 if (newExp.Members != null && newExp.Members.Count > 0)
139 {
140 for (int i = 0; i < newExp.Members.Count; i++)
141 {
142 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name);
143 if (!removeMemberInfoList.Any(member => member.Name == newExp.Members[i].Name))
144 {
145 removeMemberInfoList.Add(newExp.Members[i]);
146 }
147 }
148 }
149 }
150 else if (expandExpr.Body is UnaryExpression)
151 { //t=>t.Id
152 UnaryExpression unaryExpression = expandExpr.Body as UnaryExpression;
153 MemberExpression memberExp = unaryExpression.Operand as MemberExpression;
154 if (!memberInfoList.Any(exp => exp.Name == memberExp.Member.Name))
155 {
156 removeMemberInfoList.Add(memberExp.Member);
157 }
158 }
159 #endregion
160
161 for (int i = memberInfoList.Count - 1; i >= 0; i--)
162 {
163 if (removeMemberInfoList.Any(member => member.Name == memberInfoList[i].Name))
164 {
165 memberInfoList.Remove(memberInfoList[i]);
166 }
167 }
168 if (memberInfoList.Count <= 0)
169 {
170 throw new System.Exception("Expression Remove Error.All Properties are removed.");
171 }
172 NewObjectExpression newObjExpression = new NewObjectExpression(typeof(object).GetConstructors()[0], null, memberInfoList);
173 result = Expression.Lambda<Func<T, object>>(newObjExpression, parameter);
174 return result;
175 }
176
177 /// <summary>
178 /// Expression And
179 /// </summary>
180 /// <param name="expr"></param>
181 /// <returns></returns>
182 public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> expandExpr)
183 {
184 Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(Expression.And(expandExpr.Body, expr.Body), expr.Parameters);
185 return result;
186 }
187
188 /// <summary>
189 /// Expression And
190 /// </summary>
191 /// <param name="expr"></param>
192 /// <returns></returns>
193 public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> expandExpr)
194 {
195 Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(Expression.Or(expandExpr.Body, expr.Body), expr.Parameters);
196 return result;
197 }
198 }
Add方法可处理 NewExpression 类似 t=>new{t.Id,t.Name} , UnaryExpression 类似t=>t.Id,以及我们自定义的NewObjectExpression类型
所以我们在更新数据的时候就可以这么写了:
Dbc.Db.Update(dto, exp.Add(a => a.LastUpdateUserId));
Dbc.Db.Update(dto, exp.Add(a => new { a.LastUpdateUserId, a.UpdateTime }));
在Orm框架内部,解析NewObjectExpression时,解析方法如下
1 /// <summary>
2 /// 通过Lambed Expression获取属性名称
3 /// </summary>
4 /// <param name="expr">查询表达式</param>
5 /// <returns></returns>
6 public static List<string> GetPiList<T>(Expression<Func<T, object>> expr)
7 {
8 List<string> result = new List<string>();
9 if (expr.Body is NewExpression)
10 { // t=>new{t.Id,t.Name}
11 NewExpression nexp = expr.Body as NewExpression;
12 if (nexp.Members != null)
13 {
14 result = nexp.Members.Select(member => member.Name).ToList();
15 }
16 }
17 else if (expr.Body is NewObjectExpression)
18 { // t=>new{t.Id,t.Name}
19 NewObjectExpression nexp = expr.Body as NewObjectExpression;
20 if (nexp.Members != null)
21 {
22 result = nexp.Members.Select(member => member.Name).ToList();
23 }
24 }
25 else if (expr.Body is UnaryExpression)
26 { //t=>t.Id
27 UnaryExpression uexp = expr.Body as UnaryExpression;
28 MemberExpression mexp = uexp.Operand as MemberExpression;
29 result.Add(mexp.Member.Name);
30 }
31 else
32 {
33 throw new System.Exception("不支持的Select lambda写法");
34 }
35 return result;
36 }
至此,就完成了Expression<Func<T, object>>Add和Remove属性的扩展,Orm可以让代码更简洁.
三.后记
其实在使用新的类NewObjectExpression来解决之前,尝试过其它的许多方式,因为使用.net的类型可以在其它的框架程序中借鉴引用.不必局限在个人框架内部.
NewExpression内部有一些校验,本身Expression<Func<T, object>>是一个匿名类.试过处理NewExpression,以及新建类继承自NewExpression等方式.都没成功.
要是大家有更好的方法欢迎留言告知.希望本文能对大家有所帮助.
Mikel
