.net lambda表達式合并

【.net lambda表達式合并】事情的起因是公司一個小伙子問了我個問題 “海哥,來幫我看下這段代碼怎么不行”
Func<Report,bool> nameFilter = x=>x.Name == "test";DbContext.Report.Where(x=>x.State==1 && nameFilter(x));我一看 , 好家伙,這么騷的代碼都能讓你想出來 , 正常情況下用Linq To Object是可以這么操作的 , 但是EF的IQueryable查詢是不能這么操作的 。Linq To Object是直接執行表達式,他就是個委托方法,里面嵌套多少層委托和方法都是能直接執行的IQueryable并不會執行表達式和方法,是把表達式轉換為對應的Sql語句來執行,解析到nameFilter的時候他就懵逼了,這是啥玩意兒?。瑂ql里面沒有這種東西啊,他就轉換不了了 。
小伙子知道后明細很失望,那不能??,也不是蜗侂蠄@諼業募際? ,就是想讓小伙子能繼續他的騷操作,給他來點??怂箍萍寂c狠活 。
解決方案:
//表達式Func<Report,bool> nameFilter = x=>x.Name == "test";Func<Report,bool> stateFilter = x=>x.State==1;//合并為Func<Report,bool> whereFilter = x=>x.Name == "test" && x.State==1;//調用DbContext.Report.Where(whereFilter);完美解決
那怎么合并,當然得自己構造一個新的表達式,構造表達式需要用到Expression類 , 如果沒有用過這個類 , 可以按照下面的方式來調試看看一個表達式轉換為表達式樹是怎么樣的 。
TestExpression(x=>x.Name == "test",x=>x.State==1);public static void TestExpression(Expression<Func<Report, bool>> left,Expression<Func<Report, bool>> right){//調試查看expression對象var bodyLeft = left.Body;//這個就是x.Name == "test"var bodyRight = right.Body;//這個就是x.State==1}好 , 這里我們能獲取到表達式的Body,然后使用Expression類能很好的合并兩個表達式的body
var andAlso = Expression.AndAlso(bodyLeft ,bodyRight);//x.Name == "test" && x.State==1這樣還不行,這兩個表達式是兩個不同的委托對象,他們的參數x也是兩個不同的對象,合并了又沒完全合并
這就需要用到ExpressionVisitor類來遞歸表達式樹 , 把兩個表達式的參數替換為同一個參數 。
/// <summary>/// 替換表達式參數/// </summary>public class ReplaceExpressionVisitor : ExpressionVisitor{private Expression _leftParameter;public ReplaceExpressionVisitor(Expression leftParameter){_leftParameter= leftParameter;}protected override Expression VisitParameter(ParameterExpression node){return _leftParameter;}}最終
TestExpression(x=>x.Name == "test",x=>x.State==1);public static void TestExpression(Expression<Func<Report, bool>> left,Expression<Func<Report, bool>> right){//調試查看expression對象var bodyLeft = left.Body;//這個就是x.Name == "test"var bodyRight = right.Body;//這個就是x.State==1var leftParameter = left.Parameters[0];//表達式遞歸訪問var visitor =new ReplaceExpressionVisitor(leftParameter);//替換參數bodyRight = visitor.Visit(bodyRight);//合并表達式var expression = Expression.AndAlso(bodyLeft , bodyRight);//構建表達式var whereExpression= Expression.Lambda<Func<Report, bool>>(expression , left.Parameters);//編譯表達式var whereFilter = whereExpression.Compile();//使用DbContext.Report.Where(whereFilter);}正想給小老弟顯擺一下的時候,他又去寫其他騷代碼了
騷不過騷不過 , 完善一下列子,下面是完整的代碼
小嫩手不想動的小伙伴可以直接nuget上查找DynamicExpression.Core,直接使用
更多源碼看本人github
/// <summary>/// 替換表達式參數/// </summary>public class ReplaceExpressionVisitor : ExpressionVisitor{private Dictionary<Expression, Expression> _parameters;public ReplaceExpressionVisitor(Dictionary<Expression,Expression> parameters){_parameters = parameters;}protected override Expression VisitParameter(ParameterExpression node){if (_parameters.TryGetValue(node, out Expression _newValue)){return _newValue;}return base.Visit(node);}}/// <summary>/// 表達式擴展/// </summary>public static class ExpressionExtension{/// <summary>/// 使用AndAlso合并表達式/// </summary>/// <param name="exprs"></param>/// <returns></returns>public static Expression<T> AndAlso<T>(this IList<Expression<T>> exprs){if (exprs.Count == 0) return null;if (exprs.Count == 1) return exprs[0];var leftExpr = exprs[0];var left = leftExpr.Body;for (int i = 1; i < exprs.Count; i++){var expr = exprs[i];var visitor = GetReplaceExpressionVisitor(expr.Parameters, leftExpr.Parameters);var right = visitor.Visit(expr.Body);left = Expression.AndAlso(left, right);}return Expression.Lambda<T>(left, leftExpr.Parameters);}/// <summary>/// 使用AndAlso合并表達式/// </summary>/// <typeparam name="T"></typeparam>/// <param name="left"></param>/// <param name="right"></param>/// <returns>left AndAlso right</returns>public static Expression<T> AndAlso<T>(this Expression<T> left, Expression<T> right){return AndAlso(new List<Expression<T>>() { left, right });}/// <summary>/// 使用OrElse合并表達式/// </summary>/// <typeparam name="T"></typeparam>/// <param name="exprs"></param>/// <returns></returns>public static Expression<T> OrElse<T>(this IList<Expression<T>> exprs){if (exprs.Count == 0) return null;if (exprs.Count == 1) return exprs[0];var leftExpr = exprs[0];var left = leftExpr.Body;for (int i = 1; i < exprs.Count; i++){var expr = exprs[i];var visitor = GetReplaceExpressionVisitor(expr.Parameters, leftExpr.Parameters);var right = visitor.Visit(expr.Body);left = Expression.OrElse(left, right);}return Expression.Lambda<T>(left, leftExpr.Parameters);}/// <summary>/// 使用OrElse合并表達式/// </summary>/// <typeparam name="T"></typeparam>/// <param name="left"></param>/// <param name="right"></param>/// <returns>left OrElse right</returns>public static Expression<T> OrElse<T>(this Expression<T> left, Expression<T> right){return OrElse(new List<Expression<T>>() { left, right });}/// <summary>/// 構建visitor/// </summary>/// <param name="oldParameters"></param>/// <param name="newParameters"></param>/// <returns></returns>private static ReplaceExpressionVisitor GetReplaceExpressionVisitor(ReadOnlyCollection<ParameterExpression> oldParameters, ReadOnlyCollection<ParameterExpression> newParameters){Dictionary<Expression, Expression> dic = new Dictionary<Expression, Expression>();for (int i = 0; i < oldParameters.Count; i++){dic.Add(oldParameters[i],newParameters[i]);}return new ReplaceExpressionVisitor(dic);}}

推薦閱讀