【问题标题】:EF Core 2.1, force SelectMany to produce LEFT JOINEF Core 2.1,强制 SelectMany 产生 LEFT JOIN
【发布时间】:2018-10-02 12:47:10
【问题描述】:

假设我们有一个简单的 one->one->many 关系与下表结构:

public class City 
{
  public string Name { get; set; }

  [Column("DtaCentralSchoolId")]
  [ForeignKey("MyCentralSchool")]
  public int? CentralSchoolId { get; set; }

  public CentralSchool MyCentralSchool { get; set; }
}

public class CentralSchool
{
  public string Name { get; set; }

  [InverseProperty("MyCentralSchool")]
  public virtual IList<Student> MyStudents { get; set; }
}

public class Student 
{
  public string Name { get; set; }

  [Column("DtaCentralSchoolId")]
  [ForeignKey("MyCentralSchool")]
  public int? CentralSchoolId { get; set; }

  public CentralSchool MyCentralSchool { get; set; }
}

并尝试运行以下查询:

var result = await dbContext.Set<City>()
            .AsNoTracking()
            .SelectMany(x => x.MyCentralSchool.MyStudents.DefaultIfEmpty(), (c, s) => new {City = c, Student = s})
            .Where(x => x.Student == null || !x.Student.IsDeleted && x.Student.MyStoreId == storeId)
            .FirstOrDefaultAsync();

因此,出于某种原因,CentralSchool INNER JOIN 正在生成,而 Student 则有 LEFT JOIN,就 而言这完全没问题使用 DefaultIfEmpty()。 实际上,我也希望 CentralSchool 的 LEFT JOIN ,所以当没有 CentralSchool 时,无论如何都会有一些行出现在结果中,如何在当前构造中实现这一点,而无需手动重写丑陋的查询并强制 LEFT JOIN 出现?

更新
问题已解决,修复将在2.2中发布: https://github.com/aspnet/EntityFrameworkCore/issues/13511

【问题讨论】:

  • 您正在尝试检索学生,而不是城市。您可能应该反转查询并在每个学生中包含学校和城市,或者选择学生和相应的城市,例如.Set&lt;Students&gt;().Where(s=&gt;!s.IsDeleted &amp;&amp; s.MyStoreId==storeId).Select(s=&gt;new {Student=s,City=student.MyCentralSchool.City});。对于 this 查询,应该有 City 属性的是 School,而不是相反。如果您需要以两种方式访问​​对象,则它们应该通过适当的配置相互引用
  • @PanagiotisKanavos,对不起,如果有的话,我正在尝试准确地检索有学生的城市。这个表结构只是我用来描述问题的结构,这里的问题更具体地是关于 EF 行为,而不是关于业务逻辑。

标签: entity-framework linq entity-framework-6 entity-framework-core ef-fluent-api


【解决方案1】:

有一点突出,应该在真实代码中检查:

.Where(x => x.Student == null || !x.Student.IsDeleted && x.Student.MyStoreId == storeId)

这应该是:

.Where(x => x.Student == null || (!x.Student.IsDeleted && x.Student.MyStoreId == storeId))

无论是否有学生,这样的松散条件都可能在条件 x.Student.MyStoreId 上触发 EF,从而导致 Inner Join 条件。

编辑:我尝试重现此问题,并且使用我的架构,查询不会将 City 与 Central School 连接起来。相反,它通过 CentralSchoolId FK 将 City 连接到 Student 小瓶。我怀疑您的情况的问题是数据库没有定义 FK?数据库是通过代码优先+迁移,还是数据库优先?

查询结果:

SELECT TOP (1) 
    [Extent1].[CityId] AS [CityId], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[CentralSchoolId] AS [CentralSchoolId], 
    [Extent2].[StudentId] AS [StudentId], 
    [Extent2].[Name] AS [Name1], 
    [Extent2].[IsDeleted] AS [IsDeleted], 
    [Extent2].[CentralSchoolId] AS [CentralSchoolId1]
    FROM  [dbo].[Cities] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Students2] AS [Extent2] ON [Extent2].[CentralSchoolId] = [Extent1].[CentralSchoolId]
    WHERE ([Extent2].[StudentId] IS NULL) OR ([Extent2].[IsDeleted] <> 1)

注意:在我的情况下,我没有在学生中映射 StoreId,只是 IsDeleted。此外,由于我现有的测试区数据库中的名称冲突,表名是 Student2。

实体定义与您的相同,除了映射的 PK 并将 IsDeleted 添加到 Student。

public class City
{
    [Key]
    public int CityId { get; set; }
    public string Name { get; set; }

    [ForeignKey("MyCentralSchool")]
    public int? CentralSchoolId { get; set; }

    public virtual CentralSchool MyCentralSchool { get; set; }
}

public class CentralSchool
{
    [Key]
    public int CentralSchoolId { get; set; }
    public string Name { get; set; }

    [InverseProperty("MyCentralSchool")]
    public virtual IList<Student> MyStudents { get; set; }
}
[Table("Students2")]
public class Student
{
    [Key]
    public int StudentId { get; set; }
    public string Name { get; set; }

    public bool IsDeleted { get; set; }
    [ForeignKey("MyCentralSchool")]
    public int? CentralSchoolId { get; set; }

    public virtual CentralSchool MyCentralSchool { get; set; }
}

测试表达式运行:

var result = context.Set<City>()
    .AsNoTracking()
    .SelectMany(x => x.MyCentralSchool.MyStudents.DefaultIfEmpty(), (c, s) => new { City = c, Student = s })
    .Where(x => x.Student == null || !x.Student.IsDeleted)
    .FirstOrDefault();

我也运行它异步,并生成了相同的查询。针对 SQL Server 使用 EF6 运行。

编辑 2:确认 EF6 和 EF Core 之间的查询生成存在差异。在解决 City 和 Student 之间的关系时,EF Core 确实会在 City 和 Central School 之间产生一个内部连接,其中 EF 6 通过公共 FK 连接表来对此进行优化。我会考虑将此作为 EF Core 中的一个潜在错误提出。

假设您想要一份所有活跃学生及其相关学生的列表,并包括所有没有活跃学生的城市(因此所有城市也会被列出)

在 EF Core 中返回匹配结果的变通方法,虽然很难看:

var result2 = context.Set<City>()
    .AsNoTracking()
    .SelectMany(x => x.MyCentralSchool.MyStudents.DefaultIfEmpty(), (c, s) => new { City = c, Student = s })
    .Where(x => !x.Student.IsDeleted)
    .Union(context.Set<City>().AsNoTracking().Where(x => x.MyCentralSchool == null || !x.MyCentralSchool.MyStudents.Any(s => !s.IsDeleted))
        .Select(x => new { City = x, Student = (Student)null }))
    .ToList();

【讨论】:

  • 感谢您的建议,我已经检查过了,但不幸的是它不起作用:(我还尝试添加 x.CentralSchool 并为此进行空检查 - 不走运。
  • 好吧,这很奇怪。我已经建立了一个测试项目来查看如何重现问题,并且生成的查询没有创建内部连接,或者在 Central School 和 City 之间为几乎相同的 Linq 表达式创建任何连接。我认为您的问题可能出在数据库架构中。我已经编辑了答案以概述我发现的内容。
  • 正在检查 EF core,不好意思,刚刚在标题中看到了,但是在标签中看到了 EF6 和 Core...
  • Confirmed... 对于相同的架构,EF Core 正在生成 Inner Join,而 EF 6 没有。有趣的。 :)
  • 非常感谢您的参与!刚开了一个问题:github.com/aspnet/EntityFrameworkCore/issues/13511
猜你喜欢
  • 2021-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-23
  • 1970-01-01
  • 2021-03-22
  • 1970-01-01
  • 2020-09-19
相关资源
最近更新 更多