用点链接lambda表达式

问题描述 投票:0回答:2

我有两个表达式,我想链接它们,因此结果表达式包含两个输入表达式。

Expression<Func<IQueryable<Material>, object>> expression1 = x => x.Include(m => m.MaterialGroup);
Expression<Func<IQueryable<Material>, object>> expression2 = x => x.Include(m => m.MaterialSomething);

var expression3 = expression1.Update(expression2.Body, expression2.Parameters);

现在expression3只包含x => x.Include(m => m.MaterialSomething)所以它覆盖了第二个表达式。我希望它是x => x.Include(m => m.MaterialGroup).Include(m => m.MaterialSomething)

我打算实现的是以编程方式连接多个包含表达式,以便能够在EF Core中构建更有效的系统以进行预先加载。

编辑:这不是ANDing,ORing等问题,因为我希望这些表达式被链接(如点链接),而不是逻辑连接。

丹尼尔

c# expression iqueryable ef-core-2.2
2个回答
0
投票

因为Include是你表达的扩展方法

x => x.Include(m => m.MaterialGroup);

实际上是

x => QueryableExtensions.Include(x, m => m.MaterialGroup);

所以要链接你的表达式,你需要用另一个Include来替换Include的第一个参数

x => QueryableExtensions.Include(
  QueryableExtensions.Include(x, m => m.MaterialSomething),
  m => m.MaterialGroup);

下一个代码将执行此链接

public static Expression<Func<IQueryable<T>, object>> Chain<T>(
  params Expression<Func<IQueryable<T>, object>>[] expressions)
{
    if (expressions.Length == 0)
        throw new ArgumentException("Nothing to chain");

    if (expressions.Length == 1)
        return expressions[0];

    Expression body = expressions[0].Body;
    var parameter = expressions[0].Parameters[0];
    foreach (var expression in expressions.Skip(1))
    {
        var methodCall = (MethodCallExpression)expression.Body;
        var lambda = (UnaryExpression)methodCall.Arguments[1];

        body = Expression.Call(typeof(QueryableExtensions),
            "Include",
            new []{ typeof(T), ((LambdaExpression)lambda.Operand).Body.Type},
            body, lambda
            );
    }

    return Expression.Lambda<Func<IQueryable<T>, object>>(body, parameter);
}

用法:

var expression = Chain(expression1, expression2 /*, expression3 .... */);

你可以在线测试here

请注意,为简洁起见,此代码跳过表达式验证。


0
投票

我想添加另一种方法来存档链接lambda表达式:

添加以下静态方法可以轻松访问

public static Expression<Func<T, bool>> ConcatLambdaExpression<T>(Expression<Func<T, bool>> firstExpression, Expression<Func<T, bool>> secondExpression)
{
    var invokedThird = Expression.Invoke(secondExpression, firstExpression.Parameters.Cast<Expression>());
    var finalExpression = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(firstExpression.Body, invokedThird), firstExpression.Parameters);
    return finalExpression;
}

然后你可以这样使用它:

public PersonDTO GetAll()
{
    Expression<Func<Person, bool>> expression = x => x != null;
    expression = x => x.Name == "John";

    Expression<Func<Person, bool>> pred = x => x.LastName == "Doe" || x.LastName == "Wick";

    //result of expression would be:  
    ////expression = x.Name == "John" && (x => x.LastName == "Doe" || x.LastName == "Wick")

    expression = Utilities.ConcatLambdaExpression(expression, pred);

    var result = Context.PersonEntity.Where(expression);

    //your code mapping results to PersonDTO
    ///resultMap...            

    return resultMap;
}
© www.soinside.com 2019 - 2024. All rights reserved.