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 => 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 不起作用的地方的主要内容,如果未能解决你的问题,请参考以下文章
EF6 System.Data.Entity.Core.EntityKey 在 Entity Framework Core 中的等价物是啥?
张高兴的 Entity Framework Core 即学即用:创建第一个 EF Core 应用