之前有篇文章()我分享过表达式转换的问题,当时以为问题解决了,但后来实际应用中发现其实没有解决,也并非完全没有解决,只不过不实用,问题如下:
在讲问题之前,先来看看表达式转换的目的: 其实我们这么费劲的进行表达式转换,就是为了让我们在UI层写的表达式树条件能够最终转换成EntityFramework能够识别的表达式,之所以需要转换,那是因为EntityFramework只能识别EntityFramework自身的实体对象,如果我们是数据库优先的话,这些实体就是自动生成的,而我们的业务系统往往有独立于数据库的业务实体,这两者有些情况下相同,有些情况下非常不相同,这是我们需要转换的原因,比如TestExpression.Model.Courier是业务实体,My.FrameWork.Utilities.Test.Courier是数据库对象,看如下需求: UI层进行数据搜索数据的条件搜集: Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);
要想进行数据库查询,就需要将上面的表达式树转换成下面EntityFramework能够识别的表达式树。注意,这两个表达式虽然对象名称一样,但命名空间不同。
Expression<Func<My.FrameWork.Utilities.Test.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);
问题:
StartsWith的参数是字符串,我们知道这个字符串和其它大多数基元数据不一样,它不是值类型,而是引用类型,所以当外面的局部变量做为参数传递到表达式中时,在表达式树进行解析时还会发生MemberAccess,而上篇文章中只支持参数类型是ConstantExpression,而这里的MemberExpression在解析时就是报异常。,比如下面的表达式是可以转换的,直接传递"min",这就是一个ConstantExpression类型的参数:
Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith( " min ");
但如果这样写就不行:
string name= " min "; Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(name);
以及文章开关提到的表达式也不行:(下面的mes是一个class)
Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);
如何解决?这里分享下我解决问题的过程。 1:既然参数是ConstantExpression的情况可行,那么能否将原本不是ConstantExpression的参数变更为ConstantExpression。 有了这个想法,当时认为既然mes.Name是一个对象的属性,如果对这个属性值进行下字符串的深度复制是不是也就摆脱class的引用类型问题了,虽然摆脱了class的问题,但发现string本身就是一个引用类型,所以无论是否转换还是没能将非ConstantExpression转换成ConstantExpression的情况,也就是说下面的处理是徒劳无功的。
public static class StringDeepClone { public static string GetStringDeepClone( this string source) { string result = string.Empty; using (MemoryStream ms = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(ms, source ); source = null; ms.Position = 0; result = (( string)bf.Deserialize(ms)); } return result; } }
Expression<Func<TestExpression.Model.Courier, bool>> two2 = c => c.Value.StartsWith(mes.Name);
像这行代码,在整个表达式的上下文中是没有mes类型信息的,它是一个外部的局部变量,这样在解析时就会增大难度,后来我咨询过脑袋(),他给我提供了一个转换方法,后来由于代码不全就没继续研究了,代码的功能是可以解析这种带局部变量的MemberExpression。
3:既然解析MemberExpression没能成功,那么是否能够从MemberExpression中直接抽取出值来呢? 在网上搜索了一番,发现一老外提供了一个方法能够解决: private static object GetMemberExpressionValue(MemberExpression member) { var objectMember = Expression.Convert(member, typeof( object)); var getterLambda = Expression.Lambda<Func< object>>(objectMember); var getter = getterLambda.Compile(); return getter(); }
case ExpressionType.Call: { var be = (MethodCallExpression)node; var resultValue = string.Empty; switch (be.Arguments[ 0].NodeType) { case ExpressionType.MemberAccess: var exprssion = GetMemberExpressionValue((MemberExpression)be.Arguments[ 0]); if ( null != exprssion) { resultValue = exprssion.ToString(); } break; case ExpressionType.Constant: resultValue=((ConstantExpression)be.Arguments[ 0]).Value.ToString(); break; } var expression = GetMethodExpression((MemberExpression)be.Object, resultValue, be.Method.Name, subst); return expression.Body; }
到此,表达式树的转换终于可以在实际项目应用了,这对我们动态搜集查询条件非常有帮助,尽管这个问题困扰我超过半个月时间,但最终还是得到解决了。表达式的转换比较麻烦,如果想让自己的表达式转换功能越来越强大,那么我们需要针对不同的情况编写对应的解决方案才行,没有完美只有最适合。