我有这个查询:
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.Period
2
它会显示不同的结果。为什么?
首先,假设您在表达式中使用 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)
我可能已经从它作为解决方案开始,但我真的希望首先解决潜在的性能痛点。 ;)