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