因為項目需要使用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%以上的需求.