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等方式.都没成功.
要是大家有更好的方法欢迎留言告知.希望本文能对大家有所帮助.