我正在将软件从EF 6迁移到EF Core。在测试过程中,我注意到Linq的解释方式存在差异。
My Linq
app.Deputies
.Include(d => d.User)
.Where(d => d.User == null)
.ToList()
在EF 6中,它会产生一个查询(为了阅读而简化),如此
SELECT
d.*
FROM Deputy d
LEFT JOIN User u ON u.Id = d.UserId
WHERE u.Id IS NULL
在EF EF中,SQL看起来像这样
SELECT
d.*
FROM Deputy d
LEFT JOIN User u ON u.Id = d.UserId
WHERE d.UserId IS NULL
即使我做.Where(d => d.User.Id == null)
也不会更改生成的查询。
EF 6的配置如下所示:
.HasOptional(d => d.User).WithMany().HasForeignKey(d => d.UserId);
EF Core的配置如下所示:
.HasOne(d => d.User).WithMany().HasForeignKey(d => d.UserId);
我是否遗漏了配置或任何想法中的内容,如何在EF 6中实现相同的SQL?
(我正在使用SQL Server)
编辑:数据库上的副用户和用户之间没有FK。 (仅限于模型中)
答案 0 :(得分:3)
这两个查询
SELECT
d.*
FROM Deputy d
LEFT JOIN User u ON u.Id = d.UserId
WHERE u.Id IS NULL
和
SELECT
d.*
FROM Deputy d
LEFT JOIN User u ON u.Id = d.UserId
WHERE d.UserId IS NULL
如果副手在UserId上有外键,则语义相同。
查询之间的唯一区别是Vice具有非null UserId,但User表中不存在UserId。如果您在副手上有外键,那就不会发生。
因此,EF在这两种情况下的代码生成都是正确的。 EF Core的查询更好,因为可以在连接之前评估过滤器。
答案 1 :(得分:1)
(将我的评论转化为答案)
这是一个有趣的例子,说明实施中看似无辜的变化可能会产生意想不到的副作用。
EF6过滤联接右侧的联接:
SELECT d.*
FROM Deputy d LEFT OUTER JOIN User u
ON d.UserId = u.Id
WHERE u.Id IS NULL
左侧的EF核心过滤器:
SELECT d.*
FROM Deputy d LEFT OUTER JOIN User u
ON d.UserId = u.Id
WHERE d.UserId IS NULL
SQL查询优化器并不疯狂,它发现第二个查询可以简化为:
SELECT d.*
FROM Deputy
WHERE d.UserId IS NULL
查询2和3的查询计划是相同的:只有索引扫描,而查询1包含一个嵌套循环来组合代理和用户结果。
因此,在User.Id
和Deputy.UserId
之间存在外键约束的正常情况下,EF核心实现优于前者。但在你的情况下,没有FK。因此Deputee
可能UserId
与User
不匹配,并且它们被第二个查询过滤掉,而不是第一个查询,而LINQ查询是相同的。
差异可能非常重要,因此通常我们应该从EF-core中的这种改进的查询生成中受益(假设它是故意的)。但是,我们必须面对它,EF6版本是LINQ查询表达语义的更好的翻译。
您可以通过显式编写外部联接来解决此问题:
from d in db.Deputees
join u in db.Users on d.UserId equals u.Id into ug
from u in ug.DefaultIfEmpty() // LINQ eqivalent of outer join
where u == null
select d
...过滤u.Id
或使用Any
:
db.Deputees.Where(d => !db.Users.Any(u => u.Id == d.UserId))
...转换为NOT EXISTS
。