.net lambda expression merge

The cause of the incident was that a young man in the company asked me a question "Brother Hai, help me see why this code is not working"

Func<Report,bool> nameFilter = x=>x.Name == "test";
DbContext.Report.Where(x=>x.State==1 && nameFilter(x));

I saw it, good guy, you can think of such a coquettish code. Under normal circumstances, you can do this with Linq To Object, but you can’t do this with EF queries IQueryable.
Linq To Object directly executes the expression. It is a delegate method. How many layers of delegates and methods are nested in it can be executed directly. It does
IQueryablenot execute expressions and methods, but converts expressions into corresponding Sql statements for execution. , when he parsed the nameFilter, he was confused. What the hell is this? There is no such thing in sql, so he can't convert it.

After the young man found out, Mingxi was very disappointed. That can’t be done, and it’s not that I want to show off my skills, but I just want the young man to continue his showy operations and give him some hex technology and hard work.

solution:

//表达式
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);

perfect solution

Then how to merge, of course, you have to construct a new expression yourself. ExpressionClasses are needed to construct expressions. If you have not used this class, you can debug in the following way to see how an expression is converted into an expression tree. .


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
}

Ok, here we can get the body of the expression, and then use Expressionthe class to merge the bodies of the two expressions well

var andAlso = Expression.AndAlso(bodyLeft ,bodyRight);//x.Name == "test" && x.State==1

This is not enough, these two expressions are two different delegate objects, and their parameter x is also two different objects, merged but not completely merged

This requires the use of ExpressionVisitorclasses to recurse the expression tree and replace the parameters of the two expressions with the same parameter.

    /// <summary>
    /// 替换表达式参数
    /// </summary>
    public class ReplaceExpressionVisitor : ExpressionVisitor
    {
        private Expression _leftParameter;

        public ReplaceExpressionVisitor(Expression leftParameter)
        {
            _leftParameter= leftParameter;
        }
        
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return _leftParameter;
        }
    }

finally


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
    var 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);
}

When he was about to show off to the little brother, he went to write other showy codes

It’s not a show, but it’s a show, let’s improve the list, the following is the complete code

Friends who don’t want to move their hands can directly search on nuget DynamicExpression.Coreand use it directly

For more source code, see my 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);
        }
    }

use

string connectString = "Data Source=.;Initial Catalog=RportTest;Integrated Security=True";
var optionsBuilder = new DbContextOptionsBuilder<TestContext>();
optionsBuilder.UseSqlServer(connectString);
using (TestContext ctx = new TestContext(optionsBuilder.Options))
{

    Expression<Func<ReportData, bool>> epxr1 = report => report.ID == 2023;
    Expression<Func<ReportData, bool>> epxr2 = report => report.Name == "test1";

    var epxr3 = new List<Expression<Func<ReportData, bool>>>() { epxr1, epxr2 };

    var andPredicate = epxr3.AndAlso();
    var andQuery = ctx.ReportData.Where(andPredicate);
    string andSql = andQuery.ToQueryString();
    var andResult = andQuery.ToList();

    var orPredicate = epxr3.OrElse();
    var orQuery = ctx.ReportData.Where(orPredicate);
    string orSql = orQuery.ToQueryString();
    var orResult = orQuery.ToList();
}

Guess you like

Origin blog.csdn.net/weixin_47367099/article/details/127536474