简体   繁体   English

实体框架共享查询

[英]Entity Framework Share Queries

I don't quite know how to word this, so I'm just going to explain my scenario. 我不太清楚该如何措辞,因此我将解释我的情况。

I have a scenario where I have a TerminationDate field on an EmploymentHistory table that can be null, or a date in the future. 我有一个场景,在EmploymentHistory表上有一个TerminationDate字段,该字段可以为null或将来的日期。 EmploymentHistory joins to an Employees table and it's a 1:M relationship, where a single Employee can have multiple EmploymentHistory records. EmploymentHistory联接到一个Employees表,它是一个1:M关系,其中一个Employee可以拥有多个EmploymentHistory记录。 That Employees table joins to a lot of different places such as a Users table that represents the front end portal the Employees can log in to. Employees表联接到许多不同的地方,例如Users表,该表代表Employees可以登录的前端门户。

I frequently need to grab only the active employees. 我经常只需要抓住在职员工。 The SQL logic for being active is WHERE TerminationDate IS NULL OR TerminationDate >= GETDATE() . 处于活动状态的SQL逻辑是WHERE TerminationDate IS NULL OR TerminationDate >= GETDATE() So if TerminationDate is null or if it's set in the future. 因此,如果TerminationDate为null或在以后设置。

So in EF I have these queries like this: 因此,在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();

etc 等等

This logic shows up in about 5 different places. 该逻辑显示在大约5个不同的位置。 How do I share the EmploymentHistory.Any(eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today part of the logic when the base nav prop may be different? Tried playing with Expression<Func<T, bool>> but got no where other than I think I have a solution if I can make all the Entities that need this inherit from a Base and could always assume there's a nav prop called Employee sitting there and the Base is my T . If I try an extension method, EF Core can only evaluate it client side, not server side. 我如何共享EmploymentHistory.Any(eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today ,当基础导航道具可能不同时, EmploymentHistory.Any(eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today的逻辑部分尝试过与Expression<Func<T, bool>>但我认为我有解决方案,如果我可以使所有需要此实体的实体都从Base继承,并且可以始终假设有一个名为Employee的导航道具坐在那里,而Base是我的T尝试一种扩展方法,EF Core只能在客户端进行评估,而不能在服务器端进行评估。

You could write an expression for the EmploymentHistory : Expression<Func<EmploymentHistory, bool>> activeEmployee = eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today; 您可以为EmploymentHistory编写一个表达式: Expression<Func<EmploymentHistory, bool>> activeEmployee = eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today; Expression<Func<EmploymentHistory, bool>> activeEmployee = eh => eh.TerminationDate == null || eh.TerminationDate >= DateTime.Today;

And use it like so: 并像这样使用它:
context.Employees.Where(e => e.EmploymentHistory.Any(activeEmployee)).ToList();
and
context.Users.Where(u => u.Employee.EmploymentHistory.Any(activeEmployee)).ToList(); .

It's not a huge reduction in code, but a lot less typing, and even better if you need to change the logic for some reason and don't want to change it everywhere is shows up. 这并不是代码的大幅减少,但是却减少了很多输入,甚至在由于某种原因需要更改逻辑并且不想在任何地方都更改逻辑的情况下甚至更好。

There are a number of ways to do this. 有很多方法可以做到这一点。 Using SQL Server and EF you could 使用SQL Server和EF,您可以

  1. Create a function in SQL and import it into EF. 在SQL中创建一个函数并将其导入EF。 It's a little tricky, but it can be done. 这有点棘手,但是可以做到。
  2. Create a View on the server that does the computation and define the relationships in EF so that you can call from the view and greedy load the information that you need from there. 在执行计算的服务器上创建一个视图,并在EF中定义关系,以便您可以从视图中调用并贪婪地从那里加载所需的信息。
  3. Add a computed column to the table to show whether the user is active or not. 在表中添加一个计算列,以显示用户是否处于活动状态。
  4. Add a materialized column to the table to track whether or not the user is active and maintain that column in addition to the existing data. 在表中添加一个实例化列,以跟踪用户是否处于活动状态,并维护该列以及现有数据。

I tried option one in an older version of EF and it took forever to work through it. 我在较旧版本的EF中尝试了选项一,花了很长时间才能完成。 The performance wasn't great and I never tried it again. 表现不佳,我再也没有尝试过。 It looks like this has gotten easier in newer versions of EF though, so you're welcome to give it a try. 不过,在新版本的EF中,这似乎变得越来越容易,因此欢迎您尝试一下。

Option 2 probably offers the best balance of performance and accuracy since you can optimize the query for the view and then still greedy load the data for best efficiency. 选项2可能会在性能和准确性之间达到最佳平衡,因为您可以优化视图查询,然后仍然贪婪地加载数据以获得最佳效率。

Option 3 works a lot like option 2 and makes for the simplest C# code, but it's not going to be quite as efficient since it will have to handle the columns individually rather than in aggregate. 选项3与选项2的工作原理非常相似,并且使用最简单的C#代码,但是效率将不如以前那样高,因为它必须单独处理而不是汇总处理列。

There are times that I've found you have to use materialized information and it does offer the absolute best performance since there's no added calculation at runtime. 有时,我发现您必须使用具体化的信息,并且它确实提供了绝对最佳的性能,因为在运行时没有添加任何计算。 You risk your data being out of sync though, so I would reserve it for only the most severe cases where that performance is critical. 但是,您可能会面临数据不同步的风险,因此,我仅将其保留在性能至关重要的最严重情况下。

The bug that @Rounder referenced in his comment on @WeskerTyrant 's answer here has now been resolved in EF Core. 这@Rounder在他@WeskerTyrant的答复意见中引用的错误这里现在已经解决了在EF核心。

So, you can do something like this: 因此,您可以执行以下操作:

Expression<Func<T, bool>> IsActiveEmployee =
    eh => eh.TerminationDate == null ||
          eh.TerminationDate >= DateTime.Today

And then call it like: 然后像这样调用它:

context.Users.AsQueryable().Where(IsActiveEmployee).ToList();

Notice the addition of the .AsQueryable() that allows the .Where() to accept an Expression . 注意添加了.AsQueryable() ,它允许.Where()接受一个Expression

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM