干货!表达式树解析"框架"

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html

这应该是年前最后一篇了,接下来的时间就要陪陪老婆孩子了

关于表达式树解析也是最后一篇了,该说到的中心思想都已经说到了,理解接受能力比较好的童鞋应该已经可以举一反三了

研究表达式树的过程让我感觉微软的设计真的是非常的巧妙,也为今后我的开发之路增添了新的思路

好了 废话不多说了 这篇主要是为了解决上篇中的提问的

声明解决问题的办法有很多,我只是根据我的个人习惯和风格介绍我的解决方案,并不一定就是最好的,仅仅只是提供一种思路,大家可以根据自己或项目的实际情况酌情对待

关于问题请参考干货!表达式树解析"框架"(2)结尾

问题一db.Where<User>(u => u.Name !=null);//u.Name is not null 而非( u.Name <> null )

分析这个问题主要是在Sql中`二元表达式`有一个非常特别的情况,如果和null进行比较,那么应该用is或is not 而不是=或者<>(!=)

so~我的做法是在解析二元表达式的类中处理,如第二个参数是null,且符号是Equals或NotEqual,则使用is/is not

怎么判断第二个参数是null?

这里我打算直接判断ParserArgs.Builder中最后5个字符,如果是 " NULL" 就算是NULL了

但是这里有个问题,就是原来的操作是先加入符号,再加入Right的,所以这里也要改,改为先放入Right再插入符号

代码如下classBinaryExPRessionParser:ExpressionParser<BinaryExpression>{publicoverridevoidWhere(BinaryExpressionexpr,ParserArgsargs)

{if(ExistsBracket(expr.Left))

{

args.Builder.Append('');

args.Builder.Append('(');Parser.Where(expr.Left, args);

args.Builder.Append(')');

}else{Parser.Where(expr.Left, args);

}varindex =args.Builder.Length;if(ExistsBracket(expr.Right))

{

args.Builder.Append('');

args.Builder.Append('(');Parser.Where(expr.Right, args);

args.Builder.Append(')');

}else{Parser.Where(expr.Right, args);

}varlength =args.Builder.Length;if(length - index ==5&&args.Builder[length-5] ==''&&args.Builder[length-4] =='N'&&args.Builder[length-3] =='U'&&args.Builder[length-2] =='L'&&args.Builder[length-1] =='L')

{

Sign(expr.NodeType, index, args,true);

}else{

Sign(expr.NodeType, index, args);

}}///<summary>判断是否需要添加括号///</summary>privatestaticboolExistsBracket(Expression expr)

{vars =expr.ToString();returns !=null&& s.Length >5&& s[0] =='('&& s[1] =='(';

}privatestaticvoidSign(ExpressionType type,intindex,ParserArgs args,booluseis =false)

{switch(type)

{caseExpressionType.And:caseExpressionType.AndAlso:

args.Builder.Insert(index,"AND");

break;caseExpressionType.Equal:if(useis)

{

args.Builder.Insert(index,"IS");

}else{

args.Builder.Insert(index,"=");

}

break;caseExpressionType.GreaterThan:

args.Builder.Insert(index,">");

break;caseExpressionType.GreaterThanOrEqual:

args.Builder.Insert(index,">=");

break;caseExpressionType.NotEqual:if(useis)

{

args.Builder.Insert(index,"IS NOT");

}else{

args.Builder.Insert(index,"<>");

}

break;caseExpressionType.Or:caseExpressionType.OrElse:

args.Builder.Insert(index,"OR");

break;caseExpressionType.LessThan:

args.Builder.Insert(index,"<");

break;caseExpressionType.LessThanOrEqual:

args.Builder.Insert(index,"<=");

break;default:thrownewNotImplementedException("无法解释节点类型"+type);

}

}

... ...

}

结果db.Where<User>(u => u.Name !=null);//打印 SELECT * FROM [User] u WHERE u.[Name] IS NOT NULL

问题二db.Where<User>(u => u.Name.StartsWith("bl"));//u.Name like 'bl%'

分析这2个表达式只要运行一下就可以知道,他们是无法被解析的,原因就是:

尚未实现MethodCallExpression的解析

因为2个都属性MethodCall表达式

所以只需要实现MethodCallExpressionParser即可

MethodCallExpression 方法调用表达式

Method 表示调用的方法

Arguments 表示方法中用到的参数

Object 表示调用方法的实例对象

每种方法对应的解析都是不同的,所以我为每个方法都实现一个单独的解析函数

比如String类中的3个操作,分别对应3种Like的情况

publicstaticvoidString_StartsWith(MethodCallExpression expr, ParserArgs args)

{

}publicstaticvoidString_Contains(MethodCallExpression expr, ParserArgs args)

{

}publicstaticvoidString_EndsWith(MethodCallExpression expr, ParserArgs args)

{

}

然后将他们加入到一个键值对中(因为方法是有重载的,所以会有一样名字的方法,但是解析方式是相同的)

staticDictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> _Methods =MethodDitcInit();privatestaticDictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>>MethodDitcInit()

{

Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> dict =newDictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>>();vartype =typeof(string);foreach(varmetintype.GetMethods())

{switch(met.Name)

{case"StartsWith":

dict.Add(met, String_StartsWith);

break;case"Contains":

dict.Add(met, String_Contains);

break;case"EndsWith":

dict.Add(met, String_EndsWith);

break;default:

break;

}

}returndict;

}

调用where

publicoverridevoidWhere(MethodCallExpression expr, ParserArgs args)

{

Action<MethodCallExpression, ParserArgs>act;if(_Methods.TryGetValue(expr.Method,outact))

{

act(expr, args);return;

}thrownewNotImplementedException("无法解释方法"+expr.Method);

}

现在分别完成3个String_的函数就可以了

publicstaticvoidString_StartsWith(MethodCallExpression expr, ParserArgs args)

{

Parser.Where(expr.Object, args);

args.Builder.Append("LIKE");

Parser.Where(expr.Arguments[0], args);

args.Builder.Append("+ '%'");

}publicstaticvoidString_Contains(MethodCallExpression expr, ParserArgs args)

{

Parser.Where(expr.Object, args);

args.Builder.Append("LIKE '%' +");

Parser.Where(expr.Arguments[0], args);

args.Builder.Append("+ '%'");

}publicstaticvoidString_EndsWith(MethodCallExpression expr, ParserArgs args)

{

Parser.Where(expr.Object, args);

args.Builder.Append("LIKE '%' +");

Parser.Where(expr.Arguments[0], args);

}

结果db.Where<User>(u => u.Name.StartsWith("bl"));

db.Where<User>(u => u.Name.Contains("bl"));

db.Where<User>(u => u.Name.EndsWith("bl"));/*打印

SELECT * FROM [User] u WHERE u.[Name] LIKE 'bl' + '%'

SELECT * FROM [User] u WHERE u.[Name] LIKE '%' + 'bl' + '%'

SELECT * FROM [User] u WHERE u.[Name] LIKE '%' + 'bl'*/

问题三int[] arr = {13,15,17,19,21};

db.Where<User>(u => arr.Contains(u.Age));//u.Age in (13,15,17,19,21)

分析这个问题和刚才那个问题有很多相似之处,所以首先需要在MethodCallExpressionParser类中实现一个对应Enumerable.Contains的解析函数

但是这个方法有一个比较特殊的地方就是 他是泛型方法, 所以在从键值对中获取处理函数的时候,需要把他转为`泛型方法定义`(MethodInfo.GetGenericMethodDefinition)的才可以

publicoverridevoidWhere(MethodCallExpression expr, ParserArgs args)

{

Action<MethodCallExpression, ParserArgs>act;varkey =expr.Method;if(key.IsGenericMethod)

{

key=key.GetGenericMethodDefinition();

}if(_Methods.TryGetValue(key,outact))

{

act(expr, args);return;

}thrownewNotImplementedException("无法解释方法"+expr.Method);

}

对应的处理函数

publicstaticvoidEnumerable_Contains(MethodCallExpression expr, ParserArgs args)

{

Parser.Where(expr.Arguments[1], args);

args.Builder.Append("IN");

Parser.Where(expr.Arguments[0], args);

}

看上去似乎已经完成了,但是结果是....

int[] arr = {13,15,17,19,21};

db.Where<User>(u =>arr.Contains(u.Age));//打印//SELECT * FROM [User] u WHERE u.[Age] IN 'Demo.Program+<>c__DisplayClass0'[arr]

问题问题在于arr和u.Age一样都是MemberExpression,而MemberExpression的解析之前是这样写的

classMemberExpressionParser:ExpressionParser<MemberExpression>{publicoverridevoidWhere(MemberExpression expr, ParserArgs args)

{

Parser.Where(expr.Expression, args);

args.Builder.Append('[');

args.Builder.Append(expr.Member.Name);

args.Builder.Append(']');

}

... ...

}

显然MemberExpression有两种,一种是`虚拟的`,是不存在值的,比如u.Age,

还有一种是真实的比如上面例子中的arr,他是有真实值的

所以这个地方要改一改

代码这块地方比较难,需要理解一下

classMemberExpressionParser : ExpressionParser<MemberExpression>{publicoverridevoidWhere(MemberExpression expr, ParserArgs args)

{if(expr.ExpressionisParameterExpression)

{

Parser.Where(expr.Expression, args);

args.Builder.Append('[');

args.Builder.Append(expr.Member.Name);

args.Builder.Append(']');

}else{objectval =GetValue(expr);

args.Builder.Append('');

IEnumerator array= valasIEnumerator;if(array !=null)

{

AppendArray(args, array);

}elseif(valisIEnumerable)

{

AppendArray(args, ((IEnumerable)val).GetEnumerator());

}else{

AppendObject(args, val);

}}

}///<summary>获取成员表达式中的实际值///</summary>privatestaticobjectGetValue(MemberExpression expr)

{objectval;varfield = expr.MemberasFieldInfo;if(field !=null)

{

val=field.GetValue(((ConstantExpression)expr.Expression).Value);

}else{

val= ((PropertyInfo)expr.Member).GetValue(((ConstantExpression)expr.Expression).Value,null);

}returnval;

}///<summary>追加可遍历对象(数组或集合或简单迭代器)///</summary>privatestaticvoidAppendArray(ParserArgs args, IEnumerator array)

{if(array.MoveNext())

{

args.Builder.Append('(');

AppendObject(args, array.Current);while(array.MoveNext())

{

args.Builder.Append(',');

AppendObject(args, array.Current);

}

args.Builder.Append(')');

}else{

args.Builder.Append("NULL");

}

}///<summary>追加一般对象///</summary>publicstaticvoidAppendObject(ParserArgs args,objectval)

{if(val ==null|| val ==DBNull.Value)

{

args.Builder.Append("NULL");

}elseif(valisbool)

{

args.Builder.Append(val.GetHashCode());

}else{varcode = (int)Type.GetTypeCode(val.GetType());if(code >=5&& code <=15)//如果expr.Value是数字类型{

args.Builder.Append(val);

}else{

args.Builder.Append('\'');

args.Builder.Append(val);

args.Builder.Append('\'');

}

}

}

... ...

}

结果

int[] arr = {13,15,17,19,21};

db.Where<User>(u =>arr.Contains(u.Age));//打印//SELECT * FROM [User] u WHERE u.[Age] IN (13,15,17,19,21)

问题四如果需要使用参数化传递参数,又需要怎样修改源码呢?

分析其实这个问题是最简单的一个问题,如果已经理解这个`框架`的工作原理可以轻松解决这个问题

代码1.修改ParserArgs,使其中包含一个SqlParamete的集合,并且为了方便操作,将AppendObject的方法也移入ParserArgs,变为AddParameter

使用参数化传递还有一个好处 可以不用判断参数类型,来确定是否添加 单引号(')

publicclassParserArgs

{publicParserArgs()

{

Builder=newStringBuilder();

SqlParameters=newList<SqlParameter>();

}publicList<SqlParameter> SqlParameters {get;set; }publicStringBuilder Builder {get;privateset; }///<summary>追加参数///</summary>publicvoidAddParameter(objectobj)

{if(obj ==null|| obj ==DBNull.Value)

{

Builder.Append("NULL");

}else{stringname ="p"+SqlParameters.Count;

SqlParameters.Add(newSqlParameter(name, obj));

Builder.Append('@');

Builder.Append(name);

}

}

}

2.修改本来应该输出值的位置,改为输出参数名,并将参数加入集合

ConstantExpressionParser.Where

publicoverridevoidWhere(ConstantExpression expr, ParserArgs args)

{

args.Builder.Append('');varval =expr.Value;if(val ==null|| val ==DBNull.Value)

{

args.Builder.Append("NULL");return;

}if(valisbool)

{

args.Builder.Append(val.GetHashCode());return;

}varcode = (int)Type.GetTypeCode(val.GetType());if(code >=5&& code <=15)//如果expr.Value是数字类型{

args.Builder.Append(val);

}else{

args.Builder.Append('\'');

args.Builder.Append(val);

args.Builder.Append('\'');

}

}

原方法

改为

publicoverridevoidWhere(ConstantExpression expr, ParserArgs args)

{

args.Builder.Append('');

args.AddParameter(expr.Value);

}

MemberExpressionParser.AppendObject

///<summary>追加一般对象///</summary>publicstaticvoidAppendObject(ParserArgs args,objectval)

{if(val ==null|| val ==DBNull.Value)

{

args.Builder.Append("NULL");

}elseif(valisbool)

{

args.Builder.Append(val.GetHashCode());

}else{varcode = (int)Type.GetTypeCode(val.GetType());if(code >=5&& code <=15)//如果expr.Value是数字类型{

args.Builder.Append(val);

}else{

args.Builder.Append('\'');

args.Builder.Append(val);

args.Builder.Append('\'');

}

}

}

原方法

改为, 当然你也可以考虑删除这个方法

///<summary>追加一般对象///</summary>publicstaticvoidAppendObject(ParserArgs args,objectval)

{

args.AddParameter(val);

}

最后,调用方式进行一些修改

publicDataSet Where<T>(Expression<Func<T,bool>>expr)

{varsql ="SELECT * FROM ["+typeof(T).Name +"]";

ParserArgs a=newParserArgs();

Parser.Where(expr.Body, a);

sql+= expr.Parameters[0].Name +"WHERE"+a.Builder.ToString();

Console.WriteLine(sql);using(varadp =newSqlDataAdapter(sql, ConnectionString))

{adp.SelectCommand.Parameters.AddRange(a.SqlParameters.ToArray());//添加这一句DataSet ds =newDataSet();

adp.Fill(ds);returnds;

}

}

结果ORM db =newORM("server=192.168.0.96;database=tempdb;uid=sa;pwd=123456");

db.Where<User>(u => u.Age >18&& (u.Sex ==true|| u.Name =="blqw"));

db.Where<User>(u => u.Name !=null);

db.Where<User>(u => u.Name.StartsWith("bl"));

db.Where<User>(u => u.Name.Contains("bl"));

db.Where<User>(u => u.Name.EndsWith("bl"));int[] arr = {13,15,17,19,21};

db.Where<User>(u =>arr.Contains(u.Age));/*打印

SELECT * FROM [User] u WHERE u.[Age] > @p0 AND ( u.[Sex] = @p1 OR u.[Name] = @p2)

SELECT * FROM [User] u WHERE u.[Name] IS NOT NULL

SELECT * FROM [User] u WHERE u.[Name] LIKE @p0 + '%'

SELECT * FROM [User] u WHERE u.[Name] LIKE '%' + @p0 + '%'

SELECT * FROM [User] u WHERE u.[Name] LIKE '%' + @p0

SELECT * FROM [User] u WHERE u.[Age] IN (@p0,@p1,@p2,@p3,@p4)*/

源码下载ExpressionParser3.rar

结束语关于表达式树的解析已经全部讲完了,自己回头看看,如果没有一定功力确实看起来比较费力

虽然我已经将我知道的内容基本写出来了,不过由于表达能力有限的缘故,所以可能还有很多人看不懂吧

关于表达能力的问题,我只能说抱歉了,今后慢慢改进吧

还是那句话,如果看完觉得有不明白的地方可以跟帖提问,我有空都会回答的,如果看完觉得完全看不懂,那我就没办法了...

最后,希望大家过个好年,我自己也要过个好年,年前不发文章了,哈哈

相关链接干货!表达式树解析"框架"(1)

干货!表达式树解析"框架"(2)

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航