.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函数进行单独的解析。

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