最新设计请移步 轻量级表达式树解析框架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)