【问题标题】:SQL Difference EF 6 vs EF CoreSQL 差异 EF 6 与 EF Core
【发布时间】:2018-01-10 12:04:21
【问题描述】:

我正在将软件从 EF 6 迁移到 EF Core。在测试过程中,我注意到 Linq 的解释方式有所不同。

我的 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 Core 中,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。 (仅在模型中)

【问题讨论】:

  • 您发布的 2 个 SQL 查询是一回事。
  • 只是想知道,为什么要包括检查 null 而不是直接检查外键?即app.Deputies.Where(d => d.UserId == null).ToList()
  • 核心版本更好,因为它不需要在查询计划中加入,那你为什么要EF6版本?
  • 我以为你有。重要的区别是核心查询过滤Deputy 而不是User。这使得查询计划的效率大大提高。
  • 好的,我错过了那个。在这种情况下,EF 存储模型和实际存储模型之间存在语义差异,您有责任解决它,例如将其更改为 Any 查询(而不是 Users.Any(u.Id == d.UserId))。

标签: c# sql sql-server entity-framework entity-framework-core


【解决方案1】:

这两个查询

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 上具有外键,则语义相同。

查询之间的唯一区别是代理具有非空 UserId,但 UserId 不存在于 User 表中的情况。如果代理上有外键,则不会发生这种情况。

所以 EF 在这两种情况下的代码生成都是正确的。 EF Core 的查询更好,因为可以在连接之前评估过滤器。

【讨论】:

  • 感谢您的回答。我知道如果 UserId 上有一个 FK,它的行为会相同。但是只有模型上的数据库上没有。我可以指示模型构建器在模型中的数据库上不存在这种关系吗?或类似的东西? :)
【解决方案2】:

(把我的 cmets 变成答案)

这是一个有趣的例子,说明看似无害的实现更改可能会产生意想不到的副作用。

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.IdDeputy.UserId 之间存在外键约束的正常情况下,EF-core 实现优于前者。但是在您的情况下,没有FK。所以Deputees 可能有不匹配任何UserUserIds,它们被第二个查询过滤掉,而不是被第一个查询过滤掉,而 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-14
    • 2022-01-15
    • 2016-10-10
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    相关资源
    最近更新 更多