Entity Framework Core Linq NULL 不起作用的地方

Posted

技术标签:

【中文标题】Entity Framework Core Linq NULL 不起作用的地方【英文标题】:Entity Framework Core Linq Where NULL does not work 【发布时间】:2017-04-25 05:33:47 【问题描述】:

我正在使用 Entity Framework Core 和 Linq 编写查询,以获取对象上 EndDate 属性为 NULL 的所有条目。但是,EF 不会将查询转换为正确的 SQL 以过滤掉 EndDate 不为 NULL 的对象。我正在使用带有这些包的 mysql 数据库:

"MySql.Data.Core": "7.0.4-IR-191",
"MySql.Data.EntityFrameworkCore": "7.0.4-IR-191",

这是我的查询:

var employees = (from emp in _context.Employees.ToList()
                             join loc in _context.Locations.ToList()
                                on emp.HomeLocationId equals loc.LocationId
                             join rate in _context.EmployeeRates.ToList()
                                on emp.EmployeeId equals rate.EmployeeId
                             where rate.EndDate == null
                             select emp).ToList();

这是我的 EndDate 属性声明:

public DateTime? EndDate  get; set; 

生成的 SQL 根本不包含 WHERE 子句。我已将此查询手动转换为 MySQL,并且效果很好:

SELECT e.FirstName, e.LastName, er.Rate, er.StartDate, er.EndDate
FROM qasdb.employees as e
JOIN qasdb.employeerates as er on er.EmployeeId = e.EmployeeId
JOIN qasdb.locations as l on l.LocationId = e.HomeLocationId
WHERE e.FirstName='Todd' AND er.EndDate is null

这是 EF Core 的问题吗?是否有已知的解决方法可以使 Null 比较起作用?

编辑

这是生成的 SQL。它看起来正在做几个查询:

SELECT e.EmployeeRateId, e.EmployeeId, e.EndDate,
       e.LastModifiedBy, e.Rate, e.StartDate FROM employeerates
AS e

SELECT emp.EmployeeId, emp.ActiveFlag, emp.City,
       emp.CreateStamp, emp.Email, emp.EmergencyContactName,
       emp.EmergencyContactPhone, emp.FirstName,
       emp.HomeLocationId, emp.JobClass, emp.LastModifiedBy,
       emp.LastName, emp.PhoneNumber, emp.Ssn, emp.State,
       emp.Street, emp.UpdateStamp, emp.Zip FROM employees 
AS emp
INNER JOIN locations AS loc ON emp.HomeLocationId = loc.LocationId

编辑#2

当我从每一行中删除 ToList() 调用时,会按预期生成 SQL:

SELECT emp.EmployeeId, emp.ActiveFlag, emp.City,
       emp.CreateStamp, emp.Email, emp.EmergencyContactName,
       emp.EmergencyContactPhone, emp.FirstName,
       emp.HomeLocationId, emp.JobClass, emp.LastModifiedBy,
       emp.LastName, emp.PhoneNumber, emp.Ssn, emp.State,
       emp.Street, emp.UpdateStamp, emp.Zip FROM employees 
AS emp  
INNER JOIN locations AS loc ON emp.HomeLocationId = loc.LocationId 
INNER JOIN employeerates AS rate ON emp.EmployeeId = rate.EmployeeId 
WHERE rate.EndDate IS NULL

但是,当我删除 .ToList() 调用时,我的导航属性从 Employee 对象丢失到 EmployeeRates 列表。这是我的 Employee 实体的设置方式:

 public class Employee : BaseEntity
 
     public int EmployeeId  get; set; 

     ......


     public string JobClass  get; set; 

     public int HomeLocationId  get; set; 




     //Navigation Properties
     public virtual Location HomeLocation  get; set; 

     public virtual List<EmployeeRate> EmployeeRates  get; set; 

 

删除 ToList() 调用后,HomeLocation 和 EmployeeRates 对象都返回“null”。

【问题讨论】:

能把ef core生成的sql贴一下吗?还有你为什么要在每个 in 中调用 ToList() 方法? 删除除最终调用之外的所有ToList 调用,查询应与预期一致。目前您正在告诉 EF 加载内存中的所有表并在那里执行连接/过滤。 @IvanStoev,我按照你的建议做了,但是这样做后我的导航属性丢失了 EmployeeRates 对象 @H.Herzl,我已经用 SQL 更新了我的帖子。谢谢 【参考方案1】:

您的第一个查询在每个 DbSet 之后包含 ToList(),这会将所有这些表中的所有数据都带到内存中。每当调用 ToList() 时,EF 都会执行查询。因此,即使您一起编写了整个 linq 查询,也有 3 个小查询供 EF 处理(加载每个 DbSet),因此您会收到 3 个不同的查询发送到数据库。 EF 将从这些查询中生成实体集合,其他所有内容都将在客户端进行评估,因此您看不到 where 子句被翻译到服务器,因为它根本不是提供给 EF 的查询的一部分。在此解决方案中,您可以看到加载的导航属性,因为所有数据都加载到内存中,然后 EF 将修复导航属性以填充它们。 (这样内存中的所有数据都处于一致状态。)

当您从所有 DbSet 中删除 ToList() 调用时,它会变成一个传递给 EF 的查询。 (最后一个 ToList() 调用执行)因此 EF 将处理查询并翻译 where 子句,但由于您只投影出 Employee 对象,EF 将仅获取它的属性,不会检索任何相关数据。如果你想填充导航属性,即加载相关数据,那么你必须使用Include synatax 明确告诉 EF。

您要查找的查询是

var result = (from e in db.Employees.Include(e => e.HomeLocation).Include(e => e.EmployeeRates)
    join er in db.EmployeeRates on e.Id equals er.EmployeeId
    where er.EndDate != null
    select e).ToList();

对于您希望在最终结果中填充的每个导航,查询都有Include。由于 EF 中仍然不支持过滤的包含,(请参阅https://github.com/aspnet/EntityFramework/issues/1833)您不能直接在rate.EndDate 上指定 where 子句。因此,您需要手动加入该表并在EndDate 上应用 where 条件。由于HomeLocation 是一对一导航,EF 将仅在主查询中为其检索相关数据(以填充导航)。 EmployeeRates 是集合导航,因此 EF 将发送单独的查询以加载相关员工的相关数据。重要的一点,您不需要手动加入HomeLocation。包括将为您做到这一点。您需要手动加入 EmployeeRate 仅用于过滤。

以下是SQL中生成的主要查询

    SELECT [e].[Id], [e].[HomeLocationId], [l].[Id]
    FROM [Employees] AS [e]
    INNER JOIN [EmployeeRates] AS [er] ON [e].[Id] = [er].[EmployeeId]
    INNER JOIN [Locations] AS [l] ON [e].[HomeLocationId] = [l].[Id]
    WHERE [er].[EndDate] IS NOT NULL
    ORDER BY [e].[Id]

【讨论】:

这似乎部分工作,因为它正在加载导航属性。但是,它仍然包括日期不为空的员工对象上的employeeRates。我只查找具有 EndDate == null 的employeeRate 记录的记录。 这就是过滤后包含的功能,但尚不支持。【参考方案2】:

您是否使用代码优先?

检查你的地图类中是否没有这个

this.Property(t =&gt; t.EndDate).IsRequired();

【讨论】:

我不确定您所说的“地图类”是什么意思。你能给我一些关于在哪里看的更多见解吗?也许您的意思是我的 DbContext 类中的 OnModelCreating 方法? 你在 Ef Core 中使用什么?数据注释或 Fluent API @H.Herzl,我两者都用了一点。 Core 中缺少数据注释。比如没有外键注解,所以外键我一直在使用 Fluent API。 您可以使用数据注释进行 fk 定义,但在您的情况下,强制列似乎存在问题 从以下命名空间为您的属性添加 Key 和 ForeignKey 属性:System.ComponentModel.DataAnnotations & System.ComponentModel.DataAnnotations.Schema

以上是关于Entity Framework Core Linq NULL 不起作用的地方的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 快速开始

EF6 System.Data.Entity.Core.EntityKey 在 Entity Framework Core 中的等价物是啥?

张高兴的 Entity Framework Core 即学即用:创建第一个 EF Core 应用

Entity Framework Core 迁移命令

Entity Framework Core - 计算字段排序

Entity Framework Core 性能优化