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

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

代码如下

    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)
*/

源码下载

ExpressionParser3.rar

结束语

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

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

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

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

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

 相关链接

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

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

posted @ 2014-01-25 00:40  冰麟轻武  阅读(4263)  评论(17编辑  收藏  举报