2017-11-18 83 views
1

我为构建搜索逻辑的动态表达式树遇到了一个小问题。为实体自己的属性创建一个表达式树工作正常,但我不知道如何添加一个表达式,它将按照子实体属性进行过滤。过滤但属性和子实体属性

这是我的任务实体:

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

这里是项目实体:

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

和逻辑构建动态表达:

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

我不知道怎么样通过CustomerId进行过滤。上面代码标记为This part need's to be reworked的部分可能是错误的,或者至少是其最后部分。这个想法是扩展现有的表达式(这个由BuildExpression方法构建的表达式)和一个将由CustomerId过滤的表达式。

我已经在这方面失去了一些时间,尝试自己寻找答案,但没有结果。

任何帮助?

回答

2

由于您提供了最少的代码,因此如何创建实际表达式是未知的。所以,我会尝试为这种情况提供一个通用配方。

如果你想过滤的Task列表,那么你仍然需要使用Task类型相同​​,就像你已经这样做:

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

没有必要创建Project类型的其他​​即使您想过滤Project的属性。相反,你只需要重用前者​​。需要注意的是,如果我们建立一个静态谓词像下面,这也是我们也不使用不同的参数表达的情况:

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

我们动态地建立在导航(子)属性筛选,关键是以正确形成左表达式(即导航属性的表达式)。

可以说我们的导航属性是用点符号表示的:Project.CustomerId。然后,我们可以做这样的事情,以创建左表达财产:

// 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)); 

然后你就可以做其他像一个正常的特性,例如创建正确表达,并将其与另一种表达结合定义运算符(等于,不等于等)。

希望这会有所帮助。

+0

谢谢 - 你救了我的一天!但是当我像这样调用ToList()时,我得到“对象引用未设置为对象的实例”:'Context.Tasks.Where(searchCondition).ToList()'(搜索条件包含表达它是'IQueryable ')。堆栈跟踪:在System.Linq.Enumerable的System.Linq.Enumerable.WhereEnumerableIterator1.ToList()处的lambda_method(Closure,Task)处。ToList [TSource](IEnumerable1 source)'。 任何想法? –

+1

@DawidRutkowski很可能'Project'是'null'。如果你想填充'IQueryable ',你最好改变方法签名来返回'Expression >'并移除'Compile()'调用。目前,即使您的源代码是'IQueryable ',编译器也会调用'IEnumerable '上的Where'方法。 –

+1

@DawidRutkowski虽然我不确定,但是可能的原因会像@IvanStoev指出的那样。如果你在'searchCondition'上使用'.Compile',那么你不必这样做,除非你想避免延迟加载。相反,您可以直接将'searchCondition'应用于'IQueryable'数据,然后'ToList'来枚举。如果这不起作用,那么可以尝试包括导航属性路径(不包括路径中的最后一个属性;即简单类型的实际属性)以强制连接。 –