我不太知道如何措辞,所以我只是要解释我的场景。
我有一个方案,即EmploymentHistory
表上的TerminationDate
字段可以是 null,也可以是将来的日期。EmploymentHistory
联接到Employees
表,这是一个 1:M 关系,其中单个Employee
可以有多个EmploymentHistory
记录。 该Employees
表连接到许多不同的位置,例如表示Employees
可以登录的前端门户的Users
表。
我经常只需要抓住活跃的员工。 处于活动状态的 SQL 逻辑是WHERE TerminationDate IS NULL OR TerminationDate >= GETDATE()
。 因此,如果终止日期为空或将来设置
。所以在 EF 中,我有这样的查询:
// Grab all Active Employees
context.Employees.Where(e => e.EmploymentHistory.Any(eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today).ToList();
// Get all Users
context.Users.Where(u => u.Employee.EmploymentHistory.Any(eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today).ToList();
等
这个逻辑出现在大约 5 个不同的地方。 当基本导航道具可能不同时,如何共享逻辑的EmploymentHistory.Any(eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today
部分? 尝试玩Expression<Func<T, bool>>
但除了我认为我有一个解决方案,如果我可以让所有需要这个的实体都从基地继承,并且总是可以假设有一个叫做员工的导航道具坐在那里,基地是我的T
。 如果我尝试扩展方法,EF Core 只能评估它在客户端,而不是服务器端。
你可以为EmploymentHistory
编写一个表达式:Expression<Func<EmploymentHistory, bool>> activeEmployee = eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today;
并像这样使用它:context.Employees.Where(e => e.EmploymentHistory.Any(activeEmployee)).ToList();
和context.Users.Where(u => u.Employee.EmploymentHistory.Any(activeEmployee)).ToList();
.
这不是代码的巨大减少,而是少了很多类型,如果您出于某种原因需要更改逻辑并且不想在任何地方都显示它,那就更好了。
有许多方法可以做到这一点。 使用 SQL Server 和 EF,您可以
- 在 SQL 中创建一个函数并将其导入到 EF 中。 这有点棘手,但可以做到。
- 在执行计算的服务器上创建一个视图,并在 EF 中定义关系,以便可以从该视图调用并从那里贪婪地加载所需的信息。
- 将计算列添加到表中以显示用户是否处于活动状态。
- 将具体化列添加到表中以跟踪用户是否处于活动状态,并维护该列以及现有数据。
我在旧版本的 EF 中尝试了选项 1,并且花了很长时间才完成它。 性能不是很好,我再也没有尝试过。 不过,看起来这在较新版本的 EF 中变得更容易了,因此欢迎您尝试一下。
选项 2 可能提供了性能和准确性的最佳平衡,因为您可以优化视图的查询,然后仍然贪婪地加载数据以获得最佳效率。
选项3 的工作方式与选项 2 非常相似,并且是最简单的 C# 代码,但它的效率不会很高,因为它必须单独处理列而不是聚合处理列。
有时我发现您必须使用物化信息,并且它确实提供了绝对最佳的性能,因为在运行时没有添加计算。 但是,您可能会面临数据不同步的风险,因此我只会将其保留在性能至关重要的最严重情况下。
@Rounder在这里对@WeskerTyrant答案的评论中引用的错误现在已经在 EF Core 中得到解决。
因此,您可以执行以下操作:
Expression<Func<T, bool>> IsActiveEmployee =
eh => eh.TerminationDate == null ||
eh.TerminationDate >= DateTime.Today
然后这样称呼它:
context.Users.AsQueryable().Where(IsActiveEmployee).ToList();
请注意添加的.AsQueryable()
允许.Where()
接受Expression
。