我有这个查询:
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
,它将显示不同的结果。为什么?
答案 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)
我可能以解决方案为起点,但我确实希望能够首先克服潜在的性能难题。 ;)