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

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

 

为了过个好年,我还是赶快把这篇完成了吧

声明

  本文内容需要有一定基础的开发人员才可轻松阅读,如果有难以理解的地方可以跟帖询问,但我也不一定能回答你,原因参考文章干货!表达式树解析"框架"(1)

  本文中的栗子偏向于结构简单,便于理解,主要将的是思路,而不是具体的解决方案;了解思路之后各位程序猿们可以根据自身需求加以变化,俗话说的好:

  授人以(dai)渔(ma)不如授之以(fang)渔(fa)

  本文主要想表达的就是一种方法和思路,高玩们如果觉得栗子太简单,请不要做无意义的举动,谢谢合作... ...

书接上文

  上文中已经说到了,"框架"的基本成员都已经创建完毕了,其实这时候我们还缺少主要角色:解析器实例,但是不要着急,因为这步是最难,也是最麻烦的

  所以我这个框架的主要核心也是在这里,怎样方便灵活的编写`表达式树解析器实例`

  下面的内容我尽量说的易懂一些,因为希望照顾到一些程度比较差的同学,如果有人觉得幼稚,请忍耐

  好了,大家跟我一起做

建立新项目

  建立一个新的项目,引用ExpressionParser项目,然后建立一个实体类

  

编写演示代码

  在Main方法中写一个测试用代码,当然要引用命名空间 (我在项目ExpressionParser中的命名空间是blqw.Linq)

        static void Main()
        {
            Expression<Func<User, bool>> expr = u => u.Age == 1;//实例化表达式树
            ParserArgs arg = new ParserArgs();//实例化解析用参数
            Parser.Where(expr.Body, arg);     //调用解析方法,因为所有的Expression<T>都是继承LamdaExpression,所以解析的时候需要调用.Body获得真正的表达式树对象
        }

  直接调试运行

  好吧 看懂的同学可能要说了,不是没写解析器的实例吗,这样运行不是报错了吗

  没错,就是让他报错

观察错误信息

  果然,不错所料的报错了

  仔细观察错误信息,`尚未实现BinaryExpression的解析`,这句话说明了,刚才那个委托中,至少包含一个BinaryExpression,而我们没有对应BinaryExpression的解析器,所以出现了异常

  现在需要实现一个,继承ExpressionParser<T>,并将T限定为BinaryExpression

    class BinaryExpressionParser : ExpressionParser<BinaryExpression>
    {
    ... ...
    }

  根据前文中的介绍,解析器的命名如果遵循 xxxExpression + Parser 则会自动被框架识别,并注册;

  所以我们类的名称叫 BinaryExpressionParser 

  现在把断点加在Where方法中

调试分析

  注意观察传递进来的expr参数

  "很巧"的是,BinaryExpression在前文中也有介绍,其主要属性就是Left Right 和 NodeType

  现在需要进行进一步的解析了

  Left和Right都是表达式树 Expression对象,所以将他们继续交给框架处理

  NodeType才是需要立即处理的,由于他是枚举,所以处理起来相对容易

 

  还有一点,处理的结果保存在哪里呢?

  关于这点,暂时我把它放在ParserArgs中的Builder属性中,这个属性是一个StringBuilder

    public class ParserArgs
    {
        public ParserArgs()
        {
            Builder = new StringBuilder();
        }

        public StringBuilder Builder { get; private set; }

        public IList<ParameterExpression> Forms { get; set; }

        public readonly string[] FormsAlias = { "it", "A", "B", "C", "D", "E" };
    }
ParserArgs

  Ps一下:ParserArgs是一个灵活的对象,在这个`框架`中它没有固定的属性和字段;大家可以根据自己的需要去修改它

        private static void Sign(ExpressionType type, ParserArgs args)
        {
            switch (type)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    args.Builder.Append(" AND");
                    break;
                case ExpressionType.Equal:
                    args.Builder.Append(" =");
                    break;
                case ExpressionType.GreaterThan:
                    args.Builder.Append(" >");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    args.Builder.Append(" >=");
                    break;
                case ExpressionType.NotEqual:
                    args.Builder.Append(" <>");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    args.Builder.Append(" OR");
                    break;
                case ExpressionType.LessThan:
                    args.Builder.Append(" <");
                    break;
                case ExpressionType.LessThanOrEqual:
                    args.Builder.Append(" <=");
                    break;
                default:
                    throw new NotImplementedException("无法解释节点类型" + type);
            }
        }
处理ExpressionType

  所以处理ExpressionType 后,结果直接追加到Builder中

  Left和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);
            }
            Sign(expr.NodeType,  args);
            if (ExistsBracket(expr.Right))
            {
                args.Builder.Append(' ');
                args.Builder.Append('(');
                Parser.Where(expr.Right, args);
                args.Builder.Append(')');
            }
            else
            {
                Parser.Where(expr.Right, 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, ParserArgs args)
        {
            switch (type)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    args.Builder.Append(" AND");
                    break;
                case ExpressionType.Equal:
                    args.Builder.Append(" =");
                    break;
                case ExpressionType.GreaterThan:
                    args.Builder.Append(" >");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    args.Builder.Append(" >=");
                    break;
                case ExpressionType.NotEqual:
                    args.Builder.Append(" <>");
                    break;
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                    args.Builder.Append(" OR");
                    break;
                case ExpressionType.LessThan:
                    args.Builder.Append(" <");
                    break;
                case ExpressionType.LessThanOrEqual:
                    args.Builder.Append(" <=");
                    break;
                default:
                    throw new NotImplementedException("无法解释节点类型" + type);
            }
        }
        ... ...
    }

 

 

再次运行调试

  错误又出现了

  

  这次是MemberExpression,继续创建MemberExpressionParser,在Where方法中加断点

  

  首先自动忽略上面2个属性

  然后分析一下剩下的几个属性,有那些是有用的,可以参考MSDN

  ...  

  根据观察Expression属性是需要进一步解析的,依照惯例,交给`框架`  Parser.Where(expr.Expression,args);

  Member对应的类型是MemberInfo,这里需要有一点点反射的知识了,至少你需要知道PropertyInfo和FieldInfo都是继承自Member的

  So,最终代码如下:

    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(']');
        }
        ... ...
    }

  继续运行,异常,添加解析器,断点,分析,编写解析器,... ...

    class ParameterExpressionParser:ExpressionParser<ParameterExpression>
    {
        public override void Where(ParameterExpression expr, ParserArgs args)
        {
            args.Builder.Append(' ');
            args.Builder.Append(expr.Name);
            args.Builder.Append('.');
        }
        ... ...
    }

    class ConstantExpressionParser:ExpressionParser<ConstantExpression>
    {
        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('\'');
            }
        }
        ... ...
    }

第一次运行通过

  修改一下Main方法,让他可以输出结果

        static void Main()
        {
            
            Expression<Func<User, bool>> expr = u => u.Age == 1;//实例化表达式树
            ParserArgs arg = new ParserArgs();//实例化解析用参数
            Parser.Where(expr.Body, arg);     //调用解析方法,因为所有的Expression<T>都是继承LamdaExpression,所以解析的时候需要调用.Body获得真正的表达式树对象
            Console.WriteLine(arg.Builder.ToString());
        }

  很显然,已经有一些成果了

让我来构造一个简单的ORM

    class ORM
    {
        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))
            //{
                DataSet ds = new DataSet();
            //    adp.Fill(ds);
                return ds;
            //}
        }

        public ORM(string connectionString)
        {
            ConnectionString = connectionString;
        }

        public string ConnectionString { get; private set; }
    }

  因为实际上,我的电脑是不存在这样的数据库的,所以我注释了一部分代码

  再修改一下Main中的调用方式

static void Main()
{
    ORM db = new ORM("server=192.168.0.96;database=tempdb;uid=sa;pwd=123456");
    var ds = db.Where<User>(u => u.Age > 18 && (u.Sex == true || u.Name == "blqw"));
}

  打印结果

SELECT * FROM [User] u WHERE u.[Age] > 18 AND ( u.[Sex] = 1 OR u.[Name] = 'blqw')
请按任意键继续. . .

课后练习1

  至此,解析已经初见成效了,不过这只是一个最简单的栗子

  当然正因为是最简单的,所以遇到稍微复杂一点的表达式的时候就无法继续处理了

  而且还有bug

  不过,如果你的理解和接受能力很强,那么你可以尝试修改源码,处理以下几种情况

db.Where<User>(u => u.Name != null);            //u.Name is not null  而非( u.Name <> null )
db.Where<User>(u => u.Name.StartsWith("bl"));   //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)

课后练习2

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

本章结束

  以上2个练习的答案将在第三章中得到解答

未完待续... ...

文章中出现的源码下载:ExpressionParser2.rar

前文链接:干货!表达式树解析"框架"(1)

后文链接:干货!表达式树解析"框架"(3)

posted @ 2014-01-20 15:21  冰麟轻武  阅读(3800)  评论(10编辑  收藏  举报