天天看點

解析表達式

.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函數進行單獨的解析。

目前還存在一個問題難以解決,即如果需要反複用到某個類,由于表達式中無法确定到底是指的哪一個類,是以沒法使用重名的類進行關聯查詢。