EF Core 内部加入而不是左

EF Core Inner join instead Left

我使用 Include 的查询生成 sql,使用 Inner 加入 而不是 Left。我的 FK 可以为空,所以我无法解释这种行为。使用可为空的 FK,我希望正常 Left 加入。

我是不是漏掉了什么?

Linq 查询:

     var projectEntity = await _context.Projects
            // few more includes were omitted
            .Include(p => p.Invoice)
            .FirstOrDefaultAsync(c => c.ProjectId == id);

类:

[Table("InvoicedHistory")]
public class InvoiceHistory
{
    [Key]
    [Column("InvoicedHistory_ID")]
    public int InvoicedHistoryId { get; set; }
    // few properties omitted
    [Column("Project_ID")]
    public int? ProjectId { get; set; }
}

public class Project
{
    public int ProjectId { get; set; }
    
    // few properties were omitted
    
    [ForeignKey(nameof(InvoiceHistory.ProjectId))]
    public virtual InvoiceHistory Invoice { get; set; }
}

项目class也使用流利api:

        modelBuilder.Entity<Project>(entity =>
        {
            entity.ToTable("Projects");

            entity.HasKey(e => e.ProjectId)
                .HasName("PK_Project_Project_ID_Project");

           // few statements were omitted

        });
生成的

Sql:(很难清理此查询。它包含几个连接以包含我省略的属性的数据)

    SELECT [t].[Project_ID], [t].[Project_Client], [t].[Project_IrsDate], [t].[Project_Name], [t].[Client_ID], [t].[Client_Name], [t].[InvoicedHistory_ID], [t].[DateSubmitted], [t].[Project_ID0], [t0].[Debitor_ID], [t0].[Project_ID], [t0].[Debitor_ID0], [t0].[Address_Number], [t0].[Alias], [t0].[Alpha_Name], [t0].[Co], [t0].[Country_ID], [t0].[Currency_ID], [t0].[Havi_YesOrNo]
  FROM (
      SELECT TOP(1) [p].[Project_ID], [p].[Project_Client], [p].[Project_IrsDate], [p].[Project_Name], [c].[Client_ID], [c].[Client_Name], [i].[InvoicedHistory_ID], [i].[DateSubmitted], [i].[Project_ID] AS [Project_ID0]
      FROM [Projects] AS [p]
      INNER JOIN [Clients] AS [c] ON [p].[Project_Client] = [c].[Client_ID]
      INNER **<<<<<<<<(expect LEFT)** JOIN [InvoicedHistory] AS [i] ON [p].[Project_ID] = [i].[InvoicedHistory_ID]
      WHERE [p].[Project_ID] = 19922
  ) AS [t]
  LEFT JOIN (
      SELECT [p0].[Debitor_ID], [p0].[Project_ID], [d].[Debitor_ID] AS [Debitor_ID0], [d].[Address_Number], [d].[Alias], [d].[Alpha_Name], [d].[Co], [d].[Country_ID], [d].[Currency_ID], [d].[Havi_YesOrNo]
      FROM [ProjectDebitors] AS [p0]
      INNER JOIN [Debitors] AS [d] ON [p0].[Debitor_ID] = [d].[Debitor_ID]
  ) AS [t0] ON [t].[Project_ID] = [t0].[Project_ID]
  ORDER BY [t].[Project_ID], [t].[Client_ID], [t].[InvoicedHistory_ID], [t0].[Debitor_ID], [t0].[Project_ID], [t0].[Debitor_ID0]

看这一行-

   INNER <<<<<<<<(expect LEFT)<<<<<< JOIN [InvoicedHistory] AS [i] ON [p].[Project_ID] = [i].[InvoicedHistory_ID]

内部联接使我的查询 return 没有任何内容,因为我没有发票信息。如果我手动将其替换为 Left 加入,sql 查询将 return 我所有必要的数据。

我认为您可以使用 Fluent API 来获得您想要的结果:

modelBuilder.Entity<Project>()
  .HasOne(p => p.Invoice)
  .WithOne()
  .HasForeignKey(ih => ih.ProjectId);

这应该将其更改为左连接,因为我们没有指定 .IsRequired()

如下所述

You will not find an equivalent method in EF 7. By convention, a property whose CLR type can contain null will be configured as optional. So what decide if the relationship is optional or not is if the FK property is nullable or not respectively.

In case of your FK property is value type like int, you should declare it as nullable (int?).

现在您的注释问题很可能是以下内容与您认为的不一样:

[ForeignKey(nameof(InvoiceHistory.ProjectId))]

//Does not resolve to:
[ForeignKey("InvoiceHistory.ProjectId")]

//Does resolve to:
[ForeignKey("ProjectId")]

现在,即使这就是您要查找的内容,外键检测的操作顺序也是先检查父类型,然后再检查 属性 类型。

public class InvoiceHistory
{
    public int? ProjectId { get; set; }
}

public class Project
{
    public int ProjectId { get; set; }
    
    // this is pointing to Project.ProjectId
    // and Project.ProjectId is not nullable
    // so the join becomes an inner join
    // and really only works because they both have the same name
    [ForeignKey(nameof(InvoiceHistory.ProjectId))]
    public virtual InvoiceHistory Invoice { get; set; }
}

如果您希望它指向 属性 类型,您需要重命名 InvoiceHistory 名称:

public class InvoiceHistory
{
    public int? ProjectFk { get; set; }
}

public class Project
{
    public int ProjectId { get; set; }
    
    // this is pointing to InvoiceHistory.ProjectFk 
    // because there is no Project.ProjectFk 
    [ForeignKey(nameof(InvoiceHistory.ProjectFk))]
    public virtual InvoiceHistory Invoice { get; set; }
}

EntityFramework Data Annotations

如果你想看到它造成不良后果SQL你可以这样做:

public class InvoiceHistory
{
    public int? ProjectId { get; set; }
}

public class Project
{
    public int ProjectFk { get; set; }
    
    [ForeignKey("ProjectFk")]
    public virtual InvoiceHistory Invoice { get; set; }
}

EF 将创建:

 INNER JOIN [InvoicedHistory] AS [i] ON [p].[Project_ID] = [i].[ProjectFk]

并且会导致 SqlException 并显示类似于 Invalid column name.

的消息