Search code examples
c#entity-frameworklambdaexpression-trees

Filtering but property and child entity property


I got a small problem with building dynamic expression tree for search logic. Creating an expression tree for entity own properties is working fine, but I've no idea how to add an expression which will filter by child entity properties.

Here is my Task entity:

public class Task: Entity
{
    public TaskType Type { get; set; }
    public TaskPriority Priority { get; set; }
    public int ProjectId { get; set; }
    public Project Project { get; set; }
}

And here is Project entity:

public class Project: Entity
{        
    public int CustomerId { get; set; }
    public Customer Customer { get; set; }
}

And logic for building dynamic expression:

public Func<TaskItem, bool> Build(IList<Filter> filters)
{
    ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
    List<Filter> priorityFilter = FilterFilters(filters, "Priority");
    List<Filter> typeFilter = FilterFilters(filters, "Type");
    List<Filter> customerFilter = FilterFilters(filters, "CustomerId");

    Expression expression = null;

    // BuildExpression is a method which simply creates expression which is using Tasks properties (like Type or Priority)
    expression = BuildExpression(param, priorityFilter, expression);
    expression = BuildExpression(param, typeFilter, expression);

    // This part need's to be reworked
    ParameterExpression projectParam = Expression.Parameter(typeof(Project), "project");
    Expression projectCustomerExpression = Expression.Equal(Expression.PropertyOrField(projectParam, "CustomerId"), Expression.Constant(customerFilter[0].Value));
    Expression customerExpression = Expression.Equal(Expression.PropertyOrField(param, "Project"), projectCustomerExpression);
    Expression finall = expression != null ? Expression.AndAlso(expression, projectCustomerExpression) : projectCustomerExpression;         
    // End of filtering by CutomerId

    return Expression.Lambda<Func<TaskItem, bool>>(finall, param).Compile();
}

I've no idea how to filter by CustomerId. The part above code marked as This part need's to be reworked is probably wrong or at least the last part of it. The idea is to extend existing expression (this one build by BuildExpression method) with an expression which will filter by CustomerId.

I already lost some time on this, trying on my own and looking for answers but with no results.

Any help?


Solution

  • As you have provided a minimal code, how you are creating the actual expressions is unknown. So, I will try to provide a general recipe for this scenario.

    If you want to filter a list of Task then you still need to use the same ParameterExpression of type Task, like you have already done:

    ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
    

    There is no need to create another ParameterExpression of type Project even if you want to filter on properties of Project. In stead you just need to reuse the former ParameterExpression. Note that if we build a static predicate like below, this is also the case that we also don't use a different parameter expression:

    queryableTask.Where(t => t.Priority == TaskPriority.High && t.Project.CustomerId == 123);
    

    Now to dynamically build filter on navigational (child) property, the key is to form the left expression (i.e. expression for navigational property) correctly.

    Lets say our navigational property is in dot notation: Project.CustomerId. Then we can do something like this to create the left expression for property:

    // We already have the following commented properties
    // prop = "Project.CustomerId";
    // ParameterExpression param = Expression.Parameter(typeof(TaskItem), "task");
    var leftExpr = prop.Split('.')
                       .Aggregate<string, MemberExpression>(null, 
                          (acc, p) => acc == null 
                              ? Expression.Property(param, p) 
                              : Expression.Property(acc, p));
    

    And then you can do the rest like a normal property, such as creating the right expression and combining them with another expression defining the operator (Equal, Not Equal etc.).

    Hope this helps.