Linq動態查詢簡易解決之道(原創)


 因為項目需要使用Linq來查詢數據,但是在多條件查詢時,需要使用一大堆if(...!=string.empty)等判斷條件感覺不是很優雅。網上搜索以下,大概找到了兩種辦法,一種是老外寫的一個類,感覺用着麻煩;還有就是提供一擴展個方法,參數為某個類型,當調用該方法時,用反射去遍歷這個類型的屬性,再拿動態查詢參數和屬性值去比較,然后構建動態lambda表達式,這個也有缺陷,就是需要遍歷類型的所有屬性,而且構建lambda表達式只能構建==類型表達式,有局限性。所以自己琢磨了一個辦法,調用時只需一行代碼,lambda表達式可以隨意構建,現在進入主題。
   假設有一個類型Employee,並且該類型有集合employeeList,有這樣一個基於IEnumerable<T>類型的擴展方法Wheres(稍后介紹),那怎樣用
一行代碼employeeList.Wheres(o => o.Name == "a" && o.Salary > 5000 && o.InDate >= DateTime.Now && o.Address.Contains("北京"))去實現如下一堆代碼:

    if (!string.IsNullOrEmpty(name))
            {
                employeeList.Where(o => o.Name == name);
            }
            if (salary != null)參數設置為可空
            {
                employeeList.Where(o => o.Name == name);
            }
            if (inDate != null)
            {
                employeeList.Where(o => o.InDate>=inDate);
            }
            if (!string.IsNullOrEmpty(address))
            {
                employeeList.Where(o => o.Address.Contains("北京"));
            }

  因為Linq Lambda表達式在運行的時候會被解析成一棵表達式的二叉樹,這棵樹只允許左邊的子節點存在BinaryExpression子節點,而右邊的子節點不存在BinaryExpression子節點,但可以存在MemberExpression子節點(o.Name=="a"就是一個BinaryExpression,o.Name以及"a"就是BinaryExpression)。所以employeeList.Wheres(o => o.Name == "a" && o.Salary > 5000 && o.InDate >= DateTime.Now && o.Address == "北京")將會被解析成如下圖的一個課二叉樹:

我們只需找出紅色節點,然后得到藍色節點的值,然后去構建動態Lambda表達式就可以了,所以的獲取節點的方法如下:

 /// <summary>
        /// 分解表達式樹
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        private static Stack<Expression> DivideBinaryExpression(Expression expression)
        {
            Stack<Expression> stack = new Stack<Expression>();

            if (expression.NodeType != ExpressionType.AndAlso) //為了簡化調用代碼,只允許根節點為&&
            {
                stack.Push(expression);
            }
            else
            {
                BinaryExpression tree = expression as BinaryExpression;
                while (tree != null && tree.NodeType == ExpressionType.AndAlso)
                {
                    stack.Push(tree.Right);
                    if (tree.Left.NodeType != ExpressionType.AndAlso)
                    {
                        stack.Push(tree.Left);
                    }
                    tree = tree.Left as BinaryExpression;//循環遍歷表達式
                }
            }
            return stack;
        }
View Code

然后再根據得到的節點去動態構建Lambda表達式,方法如下:

/// <summary>
        /// 多where子句查詢
        /// </summary>
        /// <typeparam name="TSource">實體類型</typeparam>
        /// <typeparam name="TResult">Expression的返回類型</typeparam>
        /// <param name="t">實體集合</param>
        /// <param name="expression">表達式</param>
        /// <returns>實體集合</returns>
        public static IEnumerable<TSource> Wheres<TSource>(this IEnumerable<TSource> t, Expression<Func<TSource, bool>> expression)
        {
            foreach (Expression e in DivideBinaryExpression(expression.Body))
            {
                object expressionValue = null;
                if ((e as BinaryExpression) != null)
                {
                    BinaryExpression be = e as BinaryExpression;
                    expressionValue = LambdaExpression.Lambda(be.Right).Compile().DynamicInvoke();
                }
                else //為了處理像o.Address.Contains("北京")這樣的特殊節點
                {
                    MethodCallExpression mce = e as MethodCallExpression;
                    expressionValue = LambdaExpression.Lambda(mce.Arguments[0]).Compile().DynamicInvoke();
                }
                if (expressionValue != null)
                {
                    if (!string.IsNullOrEmpty(expressionValue.ToString()))
                        t = t.Where(Expression.Lambda<Func<TSource, bool>>(e, expression.Parameters).Compile());
                }
            }
            return t;
        }
View Code

在調用的時候只需像上面提到的一行代碼就夠了,雖然不是很完善,但至少能解決90%以上的需求.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM