c# - 实体框架:检索不在日期范围内的行
问题描述
我有这个查询:
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>
并且正在执行 EF Linq 查询.ToList()
。当您的数据库变得很大时,为了在将来为您节省很多痛苦,或者您在更大的项目中尝试类似的模式,您真的想避免这种情况。这种方法的问题在于,在您触及某个条款之前,EF 会检索所有实体及其关联的 ContractRow 和 Contract 记录。Where
对于具有任何大量并发请求的任何显着大小的数据表,这绝对会杀死您的系统。
对于存储库模式,我建议在绝对需要之前返回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)
我可能已经开始使用它作为解决方案,但我真的希望首先克服潜在的性能痛点。;)
推荐阅读
- javascript - 无法在onclick php中传递多个参数
- javascript - 如何计算 svg 元素以在 javascript 中进行性能调整
- ios - 删除排列的子视图时 StackView 不缩小
- acl - 在不重新启动的情况下更改 squid acl dstdomains
- javascript - iDangero.us 以前导航的 Swiper 循环错误
- django - Django模型继承reverse_name问题
- scala - Scala:Sbt 可以运行项目,但调用 jar 会导致 ClassNotFoundError
- javascript - Angular CLI 6:如何在主应用程序中使用库子项目?
- ruby - 从顶部重新启动循环
- c# - Helix 3D Toolkit - 非静态字段需要对象引用