简体   繁体   English

组合多个表达式以在 linq where 子句中使用它们

[英]Combining multiple Expression to use them in linq where clause

I'm struggling trying to combine many expressions into one to pass it to my service who whill use it to request the database.我正在努力尝试将许多表达式组合成一个表达式以将其传递给我的服务,该服务将使用它来请求数据库。

Here is my method and where i'm now with the implementation:这是我的方法,我现在在哪里实现:

private Expression<Func<EntityObject, bool>>? BuildSearchExpression()
{
    List<Expression<Func<EntityObject, bool>>> exprBuilderList = new List<Expression<Func<EntityObject, bool>>>();

    if (!string.IsNullOrWhiteSpace(filter.City))
    {
        if (filter.StringSearchChoice == StringSearchType.BeginBy)
            exprBuilderList.Add(x => x.Ville != null && x.Ville.ToLower().StartsWith(filter.City.ToLower()));
        if (filter.StringSearchChoice == StringSearchType.Contain)
            exprBuilderList.Add(x => x.Ville != null && x.Ville.ToLower().Contains(filter.City.ToLower()));
    }

    if (!string.IsNullOrWhiteSpace(filter.CompanyName))
    {
        if (filter.StringSearchChoice == StringSearchType.BeginBy)
            exprBuilderList.Add(x => x.NomEntreprise != null && x.NomEntreprise.ToLower().StartsWith(filter.CompanyName.ToLower()));
        if (filter.StringSearchChoice == StringSearchType.Contain)
            exprBuilderList.Add(x => x.NomEntreprise != null && x.NomEntreprise.ToLower().Contains(filter.CompanyName.ToLower()));
    }

    if (!string.IsNullOrWhiteSpace(filter.ContactName))
    {
        if (filter.StringSearchChoice == StringSearchType.BeginBy)
            exprBuilderList.Add(x => x.Contacts.Count > 0 && x.Contacts.Exists(con => (con.Prenom + " " + con.Nom).ToLower().StartsWith(filter.ContactName.ToLower())));
        if (filter.StringSearchChoice == StringSearchType.Contain)
            exprBuilderList.Add(x => x.Contacts.Count > 0 && x.Contacts.Exists(con => (con.Prenom + " " + con.Nom).ToLower().Contains(filter.ContactName.ToLower())));
    }

    if (!string.IsNullOrWhiteSpace(filter.Operator))
    {
        if (filter.StringSearchChoice == StringSearchType.BeginBy)
            exprBuilderList.Add(x => x.Operateur != null && x.Operateur.ToLower().StartsWith(filter.Operator.ToLower()));
        if (filter.StringSearchChoice == StringSearchType.Contain)
            exprBuilderList.Add(x => x.Operateur != null && x.Operateur.ToLower().Contains(filter.Operator.ToLower()));
    }

    if (!string.IsNullOrWhiteSpace(filter.PostalCode))
    {
        if (filter.StringSearchChoice == StringSearchType.BeginBy)
            exprBuilderList.Add(x => x.Cp != null && x.Cp.ToLower().StartsWith(filter.PostalCode.ToLower()));
        if (filter.StringSearchChoice == StringSearchType.Contain)
            exprBuilderList.Add(x => x.Cp != null && x.Cp.ToLower().Contains(filter.PostalCode.ToLower()));
    }

    if (!string.IsNullOrWhiteSpace(filter.PhoneNumber))
    {
        if (filter.StringSearchChoice == StringSearchType.BeginBy)
            exprBuilderList.Add(x => x.Tel != null && x.Tel.ToLower().StartsWith(filter.PhoneNumber.ToLower()));
        if (filter.StringSearchChoice == StringSearchType.Contain)
            exprBuilderList.Add(x => x.Tel != null && x.Tel.ToLower().Contains(filter.PhoneNumber.ToLower()));
    }

    if (filter.SheetNumber is not null)
        exprBuilderList.Add(x => x.NumFiche == filter.SheetNumber);
    if (filter.PartnerChoice != Partner.All)
        exprBuilderList.Add(x => x.Partenaire == Convert.ToBoolean((int)filter.PartnerChoice));
    if (filter.SchoolGrantOrCesu)
        exprBuilderList.Add(x => x.SubventionScolaireOuCesu == true);
 
// My first try working of course when i have a single filter available not 
// when combining multiple expression raising an InvalidOperationException
// The binary operator AndAlso is not defined for the types 
// 'System.Func`2[Domain.Entities.EntityObject,System.Boolean]' and 
// 'System.Func`2[Domain.Entities.EntityObject,System.Boolean]'
// at System.Linq.Expressions.Expression.AndAlso(Expression left, Expression right, 
// MethodInfo method)
Expression < Func<EntityObject, bool>>? resultExpr = null;
    foreach (var expr in exprBuilderList)
    {
        if (resultExpr == null)
            resultExpr = expr;
        else
            resultExpr = Expression.Lambda<Func<EntityObject, bool>>(Expression.AndAlso(resultExpr, expr), expr.Parameters);
    }
    return resultExpr;

    // My current try raising an exception of type System.ArgumentException 
    // saying my number of parameters supplied for lambda is incorrect i'm not 
    // sure what kind of parameter i have to pass?
    Type delegateType = typeof(Func<,>).GetGenericTypeDefinition().MakeGenericType(typeof(EntityObject), typeof(bool));

    var combined = exprBuilderList.Count > 0 ? 
        exprBuilderList.Cast<Expression>().Aggregate((x, y) => Expression.AndAlso(x, y)) : 
        null;

    return combined != null ? (Expression<Func<EntityObject, bool>>)Expression.Lambda(delegateType, combined) : null;
}

The method return is sending to my Service:方法返回发送到我的服务:

var (count, result) = await service.GetObjectsAsync(state.Page, state.PageSize, BuildSearchExpression());

Then i use the expression in a where clause:然后我在 where 子句中使用表达式:

public async Task<(int, IEnumerable<myModel>)> GetObjectsAsync(int page, int pageSize, Expression<Func<EntityObject, bool>>? predicate = null)
{
        var query = predicate != null ? _context.EntityObjects.Where(predicate).Include(x => x.Child) : _context.EntityObjects.Include(x => x.Child);

So I Have my db entity EntityObject and a filter class i use to dynamically build the expression tree.所以我有我的数据库实体 EntityObject 和一个过滤器 class 我用来动态构建表达式树。 The problem occur when i try to aggregate expressions.当我尝试聚合表达式时会出现问题。 I readed few articles on building Expressions trees with very complex logic using Parameters and generics, well for my case i'm not sure to have the need to go as much in complexity.我阅读了几篇关于使用参数和 generics 构建具有非常复杂逻辑的表达式树的文章,对于我的情况,我不确定是否需要 go 的复杂性。

Thanks in advance for your advices and help.提前感谢您的建议和帮助。

The simplest option would be to change your methods - your BuildSearchExpression method would return the List<Expression<Func<EntityObject, bool>>> , and the GetObjectsAsync method would simply loop through the list and pass each filter to the Where method.最简单的选择是更改您的方法 - 您的BuildSearchExpression方法将返回List<Expression<Func<EntityObject, bool>>> ,而GetObjectsAsync方法将简单地遍历列表并将每个过滤器传递给Where方法。 (Chaining multiple calls to Where is equivalent to combining the filters with AndAlso .) (将多个调用链接到Where等效于将过滤器与AndAlso组合。)

var query = _context.EntityObjects.Include(x => x.Child).AsQueryable();
foreach (Expression<Func<EntityObject, bool>> filter in exprBuilderList)
{
    query = query.Where(filter);
}

If you really want to combine all of the filters into a single predicate, you'll find that the parameter for each filter is a different object.如果您真的想将所有过滤器组合成一个谓词,您会发现每个过滤器的参数是不同的 object。 You'll need an ExpressionVisitor to replace those parameters with a single ParameterExpression instance:您需要一个ExpressionVisitor来用单个ParameterExpression实例替换这些参数:

private sealed class ReplacementVisitor : ExpressionVisitor
{
    private ReadOnlyCollection<ParameterExpression> SourceParameters { get; set; }
    private Expression ToFind { get; set; }
    private Expression ReplaceWith { get; set; }

    private Expression ReplaceNode(Expression node) 
        => node == ToFind ? ReplaceWith : node;

    protected override Expression VisitConstant(ConstantExpression node) 
        => ReplaceNode(node);

    protected override Expression VisitBinary(BinaryExpression node)
    {
        var result = ReplaceNode(node);
        if (result == node) result = base.VisitBinary(node);
        return result;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (SourceParameters.Contains(node)) return ReplaceNode(node);
        return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
    }

    private static Expression Transform(
        LambdaExpression source, 
        Expression find, 
        Expression replace)
    {
        var visitor = new ReplacementVisitor
        {
            SourceParameters = source.Parameters,
            ToFind = find,
            ReplaceWith = replace,
        };

        return visitor.Visit(source.Body);
    }

    public static Expression ReplaceParameter<TSource, TResult>(
        this Expression<Func<TSource, TResult>> expression, 
        ParameterExpression newParameter)
        => Transform(expression, expression.Parameters.Single(), newParameter)
        ?? expression.Body;
}

With that in place, you can combine your filters:有了它,您可以组合过滤器:

if (exprBuilderList.Count == 0) return null;

var x = Expression.Parameter(typeof(EntityObject), "x");
var filters = exprBuilderList.Select(fn => ReplacementVisitor.ReplaceParameter(fn, x));
var body = filters.Aggregate(Expression.AndAlso);
var result = Expression.Lambda<Func<EntityObject, bool>>(body, x);
return result;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM