天天看点

解析表达式

.net中的条件查询需要支持表达式有两个理由:

1 更为灵活,也更为直观;

2 结合c#的强类型检查,代码可靠性可以得到增强。

要在条件查询中支持表达式,有几个要求要能得到满足:

1 能支持多表联合查询;

2 子类能自动关联父类;

3 对表达式的支持要尽可能的少限制;

4 不能要求一次性输入所有的表达式,而是要能根据条件逐步的拼入表达式。

在条件查询中支持表达式,说起来好像很玄奥,但仔细想下,不就是将表达式转换为sql语句吗?所以,最简单的考虑,就是解析表达式,然后来构造sql语句的from和where字句而已。

而from字句比较简单,其实就是识别表达式中用的类,没有用过的类,就添加表名列表中,最后在构造时按顺序逐一添加到from字句即可,当然,需要为其按序号分配一个别名。

所以核心的工作就是解析表达式来构造where字句。

首先就是表达式的解析:

        private string Visit(Expression exp)

        {

            if (exp != null)

                switch (exp.NodeType)

                {

                    case ExpressionType.Negate:

                    case ExpressionType.NegateChecked:

                    case ExpressionType.Not:

                    case ExpressionType.Convert:

                    case ExpressionType.ConvertChecked:

                        return VisitUnary((UnaryExpression)exp);

                    case ExpressionType.Add:

                    case ExpressionType.AddChecked:

                    case ExpressionType.Subtract:

                    case ExpressionType.SubtractChecked:

                    case ExpressionType.Multiply:

                    case ExpressionType.MultiplyChecked:

                    case ExpressionType.Divide:

                    case ExpressionType.Modulo:

                    case ExpressionType.And:

                    case ExpressionType.AndAlso:

                    case ExpressionType.Or:

                    case ExpressionType.OrElse:

                    case ExpressionType.LessThan:

                    case ExpressionType.LessThanOrEqual:

                    case ExpressionType.GreaterThan:

                    case ExpressionType.GreaterThanOrEqual:

                    case ExpressionType.Equal:

                    case ExpressionType.NotEqual:

                        return VisitBinary((BinaryExpression)exp);

                    case ExpressionType.Constant:

                        return VisitConstant((ConstantExpression)exp);

                    case ExpressionType.Parameter:

                        return VisitParameter((ParameterExpression)exp);

                    case ExpressionType.MemberAccess:

                        return VisitMemberAccess((MemberExpression)exp);

                    case ExpressionType.Call:

                        return this.VisitMethodCall((MethodCallExpression)exp);

                    default:

                        throw new Exception(string.Format("Unhandled expression type: '{0}'", exp.NodeType));

                }

            return null;

        }

一元表达式和两元表达式的实现都是将表达式类型转换为一个操作,然后将该操作和进一步的解析出的表达式拼接到一起,如一元表达式的解析是:

        private string VisitUnary(UnaryExpression u)

        {

            if (u == null) return null;

            string rs = null;

            string op = null;

            switch (u.NodeType)

            {

                case ExpressionType.Negate:

                case ExpressionType.NegateChecked:

                    op = "-";

                    break;

                case ExpressionType.Not:

                    op = "Not ";

                    break;

                case ExpressionType.Convert:

                case ExpressionType.ConvertChecked:

                    return Visit(u.Operand);

            }

            rs = String.Format("({0}{1})", op, Visit(u.Operand));

            return rs;

        }

常量的解析首先看能否常量的类型是否能直接转换,如果不能则将该表达式动态执行取其结果:

        private string VisitConstant(ConstantExpression c)

        {

            if (c == null) return null;

            string rs = Common.TransObjToSqlValue(c.Value);

            if (rs == null && !Common.CanTrans(c.Value))

            {

                LambdaExpression lambda = Expression.Lambda(c);

                Delegate fn = lambda.Compile();

                var obj = fn.DynamicInvoke(null);

                rs = Common.TransObjToSqlValue(obj);

            }

            return rs;

        }

参数的解析稍微复杂点,主要需要判断该参数是否是给的类的父类/祖类中的成员,如果是还需要判断该父类/祖类是否已经被添加到所有数据表中了,如果没有需要将其引用过来并和该子类自动进行关联。

成员引用的解析主要是将成员首先判断是否是参数,如果是那么则按参数进行解析,否则还是要视为一个动态的表达式,执行后取其值。

方法调用的解析最为复杂,因为,方法可以有多个参数、参数还有可能需要进一步的解析,后来考虑到为了进行查询而构造的表达式,其调用的方法应该不会如此复杂,所以对方法调用所支持的表达式进行了限制,如参数只能由一个,且为简单数值等等:

       private string VisitMethodCall(MethodCallExpression c)

        {

            if (c == null) return null;

            string rs = null;

            if (c.Object != null)

            {

                //实例对象的方法调用,没有参数调用

                if (c.Object.NodeType == ExpressionType.Constant)

                {

                    //实例对象是常量

                    LambdaExpression lambda = Expression.Lambda(c);

                    Delegate fn = lambda.Compile();

                    rs = Common.TransObjToSqlValue(fn.DynamicInvoke(null));

                }

                else if (c.Object.NodeType == ExpressionType.MemberAccess)

                {

                    LambdaExpression lambda = Expression.Lambda(c.Object);

                    Delegate fn = lambda.Compile();

                    var obj = fn.DynamicInvoke(null);

                    ParameterExpression paramObj = Expression.Parameter(typeof(object), "entity");

                    var paramEntityObj = Expression.Convert(paramObj, c.Object.Type);

                    var exp = Expression.Call(paramEntityObj, c.Method);

                    var l = Expression.Lambda<Func<object, object>>(exp, paramObj);

                    Func<object, object> d = l.Compile();

                    var o = d(obj);

                    rs = Common.TransObjToSqlValue(o);

                }

            }

            else

            {

                //静态方法调用,只有一个参数

                LambdaExpression lambda = Expression.Lambda(c.Arguments[0]);

                Delegate fn = lambda.Compile();

                var obj = fn.DynamicInvoke(null);

                var o = c.Method.Invoke(null, new object[] { obj });

                rs = Common.TransObjToSqlValue(o);

            }

            return rs;

        }

有了表达式的解析,我们就可以定义创建函数和按条件添加表达式的函数了:

        public static ExpressionVisitor_Sql Create<T>(Expression<Func<T, bool>> predicate)

        {

            var ev = new ExpressionVisitor_Sql();

            var ty = typeof(T);

            ev.m_TableList.Add(ty.Name, 1);

            if (predicate != null)

                ev.m_SqlWhere = " Where " + ev.Visit(predicate.Body);

            return ev;

        }

以及:

        public void Append<T1, T2>(bool AppendIf, Expression<Func<T1, T2, bool>> predicate)

        {

            var ty = typeof(T1);

            if (AppendIf && m_TableList.InList(ty.Name) && predicate != null)

            {

                int count = m_TableList.Count + m_TableList2.Count;

                ty = typeof(T2);

                if (m_TableList.InList(ty.Name))

                    m_TableList2.Add(ty.Name, count + 1);

                else

                    m_TableList.Add(ty.Name, count + 1);

                if (m_SqlWhere == null)

                    m_SqlWhere = " Where " + Visit(predicate.Body);

                else

                    m_SqlWhere += " And " + Visit(predicate.Body);

            }

        }

当然还可以定义支持更多泛型参数表达式的类似函数。

这里面有一个比较特殊的地方,就是字符串和其它类型有一个不同点在于,字符串有一个包含操作,而包含操作不像大于、等于之类的比较操作在表达式中有明确的对应关系可供解析,所以最后用一个单独的Append_Like函数进行单独的解析。

目前还存在一个问题难以解决,即如果需要反复用到某个类,由于表达式中无法确定到底是指的哪一个类,所以没法使用重名的类进行关联查询。