我需要在一个ObjectSet上进行过滤,以便通过以下操作获得所需的实体:
query = this.ObjectSet.Where(x => x.TypeId == 3); // this is just an example;
在代码的后面(延迟执行之前),我再次按照以下方式过滤查询:
query = query.Where(<another lambda here ...>);
目前为止,那很好用。
以下是我的问题:
实体包含一个DateFrom属性和一个DateTo属性,它们都是DataTime类型。它们表示一个时间段。
我需要过滤实体,仅获取那些属于时间段的集合中的实体。集合中的时间段不一定是连续的,因此检索实体的逻辑如下:
entities.Where(x => x.DateFrom >= Period1.DateFrom and x.DateTo <= Period1.DateTo)
||
entities.Where(x => x.DateFrom >= Period2.DateFrom and x.DateTo <= Period2.DateTo)
||
...并且对于集合中的所有周期等等不断进行。
我尝试过这样做:
foreach (var ratePeriod in ratePeriods)
{
var period = ratePeriod;
query = query.Where(de =>
de.Date >= period.DateFrom && de.Date <= period.DateTo);
}
但是一旦我启动延迟执行,它就像我想要的那样将其转换为SQL(每个时间段一个过滤器,与集合中存在的时间段一样多),但是,它将其转换为AND比较而不是OR比较,这根本不返回实体,因为一个实体显然不能属于多个时间段。
我需要在此构建某种动态linq以聚合期间过滤器。
更新
基于hatten的答案,我添加了以下成员:
private Expression<Func<T, bool>> CombineWithOr<T>(Expression<Func<T, bool>> firstExpression, Expression<Func<T, bool>> secondExpression)
{
// Create a parameter to use for both of the expression bodies.
var parameter = Expression.Parameter(typeof(T), "x");
// Invoke each expression with the new parameter, and combine the expression bodies with OR.
var resultBody = Expression.Or(Expression.Invoke(firstExpression, parameter), Expression.Invoke(secondExpression, parameter));
// Combine the parameter with the resulting expression body to create a new lambda expression.
return Expression.Lambda<Func<T, bool>>(resultBody, parameter);
}
声明了一个新的CombineWithOr表达式:
Expression<Func<DocumentEntry, bool>> resultExpression = n => false;
我把它用在我的周期集合迭代中,就像这样:
foreach (var ratePeriod in ratePeriods)
{
var period = ratePeriod;
Expression<Func<DocumentEntry, bool>> expression = de => de.Date >= period.DateFrom && de.Date <= period.DateTo;
resultExpression = this.CombineWithOr(resultExpression, expression);
}
var documentEntries = query.Where(resultExpression.Compile()).ToList();
我查看了生成的SQL语句,好像这个表达式没有任何效果。生成的SQL语句返回了之前编程的过滤器,但没有返回组合过的过滤器。为什么呢?
更新2
我想尝试一下feO2x的建议,所以我已经重写了我的过滤查询,就像这样:
query = query.AsEnumerable()
.Where(de => ratePeriods
.Any(rp => rp.DateFrom <= de.Date && rp.DateTo >= de.Date))
正如您所见,我添加了AsEnumerable()
,但编译器给出了一个错误,无法将IEnumerable转换回IQueryable,因此我在查询末尾添加了ToQueryable()
:
query = query.AsEnumerable()
.Where(de => ratePeriods
.Any(rp => rp.DateFrom <= de.Date && rp.DateTo >= de.Date))
.ToQueryable();
一切工作正常,我能够编译代码并启动此查询。然而,它不能满足我的需求。
在对结果SQL进行性能剖析时,我发现过滤不是SQL查询的一部分,因为它在处理过程中通过内存过滤日期。我猜你已经知道了这一点,这也是你建议的意图。
你的建议是可行的,但是由于它在内存中过滤之前从数据库中获取所有实体(而且有成千上万个),从数据库中返回如此庞大的实体数量非常慢。
我真正想要的是将时间段过滤作为结果SQL查询的一部分发送,这样在完成过滤流程之前就不会返回大量实体。
query = query.AsEnumerable()
?这就是为什么你的查询不是你的 SQL 查询的一部分的原因吗?我不明白将其转换为可枚举类型,然后再转换回可查询类型的原因。 - hattenn