实体框架:检索不在日期范围内的行

时间:2019-08-29 17:01:14

标签: c# entity-framework linq

我有这个查询:

var query = _repository.GetAllIncluding(x => x.ContractRow, x => x.ContractRow.Contract)
                       .Where(x => (int)x.ContractRow.PeriodicityType == (int)CommonConsts.PeriodicityType.Yearly && 
                                   x.ContractRow.Contract.Date.AddYears(-(x.ContractRow.Period.Value / 2)) > x.DueDate
                                   || x.ContractRow.Contract.Date.AddYears(x.ContractRow.Period.Value / 2) < x.DueDate);

位置:

  • ContractRow.Contract.IssueDate的类型为DateTime
  • ContractRow.Period的类型为short?
  • DueDate的类型为DateTime

这些类型不能更改。

问题出在AddYears()函数中。

如果我使用.AddYears(-(2 / 2)),它将返回我期望的值,但是如果我使用.AddYears(-(x.ContractRow.Period.Value / 2)),其中ContractRow.Period2,它将显示不同的结果。为什么?

1 个答案:

答案 0 :(得分:1)

首先,假设您在表达式中使用DateTime.AddYears,这大喊您的存储库方法返回的是IEnumerable<Entity>而不是IQueryable<Entity>,并且正在使用.ToList()执行EF Linq查询。为了将来在数据库变大时减轻您的痛苦,或者在较大的项目中尝试类似的模式时,您确实想避免这种情况。这种方法的问题在于,EF甚至在触及Where子句之前,都正在检索所有实体及其关联的ContractRow和Contract记录。对于具有大量并发请求的任何规模的数据表,这绝对会杀死您的系统。

对于存储库模式,我建议返回IQueryable<Entity>并避免使用ToList之类的调用,直到绝对需要它们为止。因此,GetAll方法类似于:

public IQueryable<Row> GetAll()
{
   var query = _context.Rows.AsQueryable();
   return query;
}

请注意,我们无需打扰Include()语句等。Linq查询可以愉快地引用相关实体作为表达式的一部分,EF会自动解析它们。使用Select()投影结果还将解析相关实体。唯一需要Include()的地方就是您特别想加载和使用整个实体结构的地方。通常,这只是更新方案。在这种情况下,您可以在调用存储库方法之后在查询中添加.Include()语句,而无需将表达式传递给该方法。它还使您可以灵活地执行.Count().Any()以及使用.OrderBy().Skip(n).Take(m)等进行分页(非常简单而灵活)

对于存储库方法,以上是一个没有基本条件的简单示例。存储库为测试提供了很好的隔离点,也为常见的全局规则(例如,具有软删除(IsActive)限制和身份验证/授权检查)提供了良好的基础。例如,如果您有一个软删除系统,并且默认为活动记录:

public IQueryable<Row> GetAll(bool includeInactive = false)
{
   var query = includeInactive 
      ? _context.Rows.AsQueryable()
      : _context.Rows.Where(x => x.IsActive);
   return query;
}

大多数实体不需要includeInactive选项时,它们仅返回Where(x => x.IsActive)

这将有助于解决将来的性能问题,但是现在引起了一个您可能已经看到的问题,AddYears不能在EF Linq表达式中使用。这是因为EF尝试将您的表达式转换为SQL,而SQL无法理解.AddYears。幸运的是,EF支持处理此问题:EntityFunctions

使用IQueryable<T>存储库方法和EntityFunctions.AddYears,您将拥有:

var query = _repository.GetAll()
    .Where(x => (int)x.ContractRow.PeriodicityType == (int)CommonConsts.PeriodicityType.Yearly 
        && (EntityFunctions.AddYears(x.ContractRow.Contract.Date, (x.ContractRow.Period.Value/-2)) > x.DueDate
            || EntityFunctions.AddYears(x.ContractRow.Contract.Date, x.ContractRow.Period.Value / 2)) < x.DueDate));

最后,可能是造成悲伤的原因:操作,混合AND和OR ...(您可能在上面的示例中发现了这一点)

criteria = A AND B OR C
vs.
criteria = A AND (B OR C)

将产生不同的结果。您需要在日期范围检查周围加上括号,因为没有它们,您将得到:

WHERE PeriodicType = Yearly AND Date > 2 years ago
   OR Date < 2 years from future (and PeriodicType can be anything it wants)

A AND B OR C == (A AND B) OR C
you want 
A AND (B OR C)

我可能以解决方案为起点,但我确实希望能够首先克服潜在的性能难题。 ;)