这应该是年前最后一篇了,接下来的时间就要陪陪老婆孩子了
关于表达式树解析也是最后一篇了,该说到的中心思想都已经说到了,理解接受能力比较好的童鞋应该已经可以举一反三了
研究表达式树的过程让我感觉微软的设计真的是非常的巧妙,也为今后我的开发之路增添了新的思路
好了 废话不多说了 这篇主要是为了解决上篇中的提问的
声明
解决问题的办法有很多,我只是根据我的个人习惯和风格介绍我的解决方案,并不一定就是最好的,仅仅只是提供一种思路,大家可以根据自己或项目的实际情况酌情对待
关于问题请参考干货!表达式树解析"框架"(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再插入符号
代码如下
class BinaryExpressionParser : ExpressionParser<BinaryExpression> { public override void Where(BinaryExpression expr, ParserArgs args) { if (ExistsBracket(expr.Left)) { args.Builder.Append(‘ ‘); args.Builder.Append(‘(‘); Parser.Where(expr.Left, args); args.Builder.Append(‘)‘); } else { Parser.Where(expr.Left, args); } var index = 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); } var length = 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> private static bool ExistsBracket(Expression expr) { var s = expr.ToString(); return s != null && s.Length > 5 && s[0] == ‘(‘ && s[1] == ‘(‘; } private static void Sign(ExpressionType type, int index, ParserArgs args, bool useis = false) { switch (type) { case ExpressionType.And: case ExpressionType.AndAlso: args.Builder.Insert(index, " AND"); break; case ExpressionType.Equal: if (useis) { args.Builder.Insert(index, " IS"); } else { args.Builder.Insert(index, " ="); } break; case ExpressionType.GreaterThan: args.Builder.Insert(index, " >"); break; case ExpressionType.GreaterThanOrEqual: args.Builder.Insert(index, " >="); break; case ExpressionType.NotEqual: if (useis) { args.Builder.Insert(index, " IS NOT"); } else { args.Builder.Insert(index, " <>"); } break; case ExpressionType.Or: case ExpressionType.OrElse: args.Builder.Insert(index, " OR"); break; case ExpressionType.LessThan: args.Builder.Insert(index, " <"); break; case ExpressionType.LessThanOrEqual: args.Builder.Insert(index, " <="); break; default: throw new NotImplementedException("无法解释节点类型" + 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的情况
public static void String_StartsWith(MethodCallExpression expr, ParserArgs args) { } public static void String_Contains(MethodCallExpression expr, ParserArgs args) { } public static void String_EndsWith(MethodCallExpression expr, ParserArgs args) { }
然后将他们加入到一个键值对中(因为方法是有重载的,所以会有一样名字的方法,但是解析方式是相同的)
static Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> _Methods = MethodDitcInit(); private static Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> MethodDitcInit() { Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>> dict = new Dictionary<MethodInfo, Action<MethodCallExpression, ParserArgs>>(); var type = typeof(string); foreach (var met in type.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; } } return dict; }
调用where
public override void Where(MethodCallExpression expr, ParserArgs args) { Action<MethodCallExpression, ParserArgs> act; if (_Methods.TryGetValue(expr.Method,out act)) { act(expr, args); return; } throw new NotImplementedException("无法解释方法" + expr.Method); }
现在分别完成3个String_的函数就可以了
public static void String_StartsWith(MethodCallExpression expr, ParserArgs args) { Parser.Where(expr.Object, args); args.Builder.Append(" LIKE"); Parser.Where(expr.Arguments[0], args); args.Builder.Append(" + ‘%‘"); } public static void String_Contains(MethodCallExpression expr, ParserArgs args) { Parser.Where(expr.Object, args); args.Builder.Append(" LIKE ‘%‘ +"); Parser.Where(expr.Arguments[0], args); args.Builder.Append(" + ‘%‘"); } public static void String_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)的才可以
public override void Where(MethodCallExpression expr, ParserArgs args) { Action<MethodCallExpression, ParserArgs> act; var key = expr.Method; if (key.IsGenericMethod) { key = key.GetGenericMethodDefinition(); } if (_Methods.TryGetValue(key, out act)) { act(expr, args); return; } throw new NotImplementedException("无法解释方法" + expr.Method); }
对应的处理函数
public static void Enumerable_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的解析之前是这样写的
class MemberExpressionParser:ExpressionParser<MemberExpression> { public override void Where(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,他是有真实值的
所以这个地方要改一改
代码
这块地方比较难,需要理解一下
class MemberExpressionParser : ExpressionParser<MemberExpression> { public override void Where(MemberExpression expr, ParserArgs args) { if (expr.Expression is ParameterExpression) { Parser.Where(expr.Expression, args); args.Builder.Append(‘[‘); args.Builder.Append(expr.Member.Name); args.Builder.Append(‘]‘); } else { object val = GetValue(expr); args.Builder.Append(‘ ‘); IEnumerator array = val as IEnumerator; if (array != null) { AppendArray(args, array); } else if(val is IEnumerable) { AppendArray(args, ((IEnumerable)val).GetEnumerator()); } else { AppendObject(args, val); } } } /// <summary> 获取成员表达式中的实际值 /// </summary> private static object GetValue(MemberExpression expr) { object val; var field = expr.Member as FieldInfo; if (field != null) { val = field.GetValue(((ConstantExpression)expr.Expression).Value); } else { val = ((PropertyInfo)expr.Member).GetValue(((ConstantExpression)expr.Expression).Value, null); } return val; } /// <summary> 追加可遍历对象(数组或集合或简单迭代器) /// </summary> private static void AppendArray(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> public static void AppendObject(ParserArgs args, object val) { if (val == null || val == DBNull.Value) { args.Builder.Append("NULL"); } else if (val is bool) { args.Builder.Append(val.GetHashCode()); } else { var code = (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
使用参数化传递还有一个好处 可以不用判断参数类型,来确定是否添加 单引号(‘)
public class ParserArgs { public ParserArgs() { Builder = new StringBuilder(); SqlParameters = new List<SqlParameter>(); } public List<SqlParameter> SqlParameters { get; set; } public StringBuilder Builder { get; private set; } /// <summary> 追加参数 /// </summary> public void AddParameter(object obj) { if (obj == null || obj == DBNull.Value) { Builder.Append("NULL"); } else { string name = "p" + SqlParameters.Count; SqlParameters.Add(new SqlParameter(name, obj)); Builder.Append(‘@‘); Builder.Append(name); } } }
2.修改本来应该输出值的位置,改为输出参数名,并将参数加入集合
ConstantExpressionParser.Where
public override void Where(ConstantExpression expr, ParserArgs args) { args.Builder.Append(‘ ‘); var val = expr.Value; if (val == null || val == DBNull.Value) { args.Builder.Append("NULL"); return; } if (val is bool) { args.Builder.Append(val.GetHashCode()); return; } var code = (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(‘\‘‘); } }
改为
public override void Where(ConstantExpression expr, ParserArgs args) { args.Builder.Append(‘ ‘); args.AddParameter(expr.Value); }
MemberExpressionParser.AppendObject
/// <summary> 追加一般对象 /// </summary> public static void AppendObject(ParserArgs args, object val) { if (val == null || val == DBNull.Value) { args.Builder.Append("NULL"); } else if (val is bool) { args.Builder.Append(val.GetHashCode()); } else { var code = (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> public static void AppendObject(ParserArgs args, object val) { args.AddParameter(val); }
最后,调用方式进行一些修改
public DataSet Where<T>(Expression<Func<T, bool>> expr) { var sql = "SELECT * FROM [" + typeof(T).Name + "] "; ParserArgs a = new ParserArgs(); Parser.Where(expr.Body, a); sql += expr.Parameters[0].Name + " WHERE" + a.Builder.ToString(); Console.WriteLine(sql); using (var adp = new SqlDataAdapter(sql, ConnectionString)) { adp.SelectCommand.Parameters.AddRange(a.SqlParameters.ToArray());//添加这一句 DataSet ds = new DataSet(); adp.Fill(ds); return ds; } }
结果
ORM db = new ORM("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) */
源码下载
结束语
关于表达式树的解析已经全部讲完了,自己回头看看,如果没有一定功力确实看起来比较费力
虽然我已经将我知道的内容基本写出来了,不过由于表达能力有限的缘故,所以可能还有很多人看不懂吧
关于表达能力的问题,我只能说抱歉了,今后慢慢改进吧
还是那句话,如果看完觉得有不明白的地方可以跟帖提问,我有空都会回答的,如果看完觉得完全看不懂,那我就没办法了...
最后,希望大家过个好年,我自己也要过个好年,年前不发文章了,哈哈