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



我有这个查询:

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它会显示不同的结果。为什么?

首先,假设您在表达式中使用 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()语句,而无需将表达式传递给该方法。它还使您可以灵活地使用.OrderBy().Skip(n).Take(m)等执行.Count().Any()和分页(非常简单灵活(

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

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

大多数实体不需要包含非活动选项,它们只需返回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));

最后,你悲伤的可能原因:操作,混合和ORs......(你可能在上面的例子中发现了它(

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)

我可能已经从它作为解决方案开始,但我真的希望首先解决潜在的性能痛点。 ;)

最新更新