dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.68k stars 3.16k forks source link

Improve join table FK detection #33603

Open kamazheng opened 5 months ago

kamazheng commented 5 months ago

modelBuilder.Entity<LaborResourceGroup>()
            .HasMany(e => e.LaborResources)
            .WithMany(e => e.LaborResourceGroups)
            .UsingEntity<LaborVsGroup>();

public class LaborVsGroup : IAuditableEntity, IWriteAccessEntity
{
    [Key]
    public int Id { get; set; }

    [ForeignKey(nameof(LaborResource))]
    public int LaborResourceId { get; set; }

    [ForeignKey(nameof(LaborResourceGroup))]
    public int LaborResourceGroupId { get; set; }

    public string CreatedBy { get; set; } = string.Empty;
    public DateTime CreatedOn { get; set; }
    public virtual LaborResource LaborResource { get; set; } = null!;
    public virtual LaborResourceGroup LaborResourceGroup { get; set; } = null!;
}
public class LaborResource : BaseObject, IAuditableEntity, IWriteAccessEntity
{
    public string? Email { get; set; }
    public string CreatedBy { get; set; } = string.Empty;
    public DateTime CreatedOn { get; set; }
    public virtual List<LaborResourceGroup>? LaborResourceGroups { get; } = [];
}
public class LaborResourceGroup : BaseObject, IAuditableEntity, IWriteAccessEntity
{
    public string CreatedBy { get; set; } = string.Empty;
    public DateTime CreatedOn { get; set; }
    public virtual List<LaborResource>? LaborResources { get; } = [];
}

Generate Migration:

migrationBuilder.CreateTable(
    name: "LaborVsGroup",
    schema: "Reference",
    columns: table => new
    {
        Id = table.Column<int>(type: "int", nullable: false)
            .Annotation("SqlServer:Identity", "1, 1"),
        LaborResourceId = table.Column<int>(type: "int", nullable: false),
        LaborResourceGroupId = table.Column<int>(type: "int", nullable: false),
        CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: false),
        CreatedOn = table.Column<DateTime>(type: "datetime2", nullable: false),
        LaborResourceGroupsId = table.Column<int>(type: "int", nullable: false)
    },
    constraints: table =>
    {
        table.PrimaryKey("PK_LaborVsGroup", x => x.Id);
        table.ForeignKey(
            name: "FK_LaborVsGroup_LaborResourceGroup_LaborResourceGroupId",
            column: x => x.LaborResourceGroupId,
            principalSchema: "Reference",
            principalTable: "LaborResourceGroup",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        table.ForeignKey(
            name: "FK_LaborVsGroup_LaborResourceGroup_LaborResourceGroupsId",
            column: x => x.LaborResourceGroupsId,
            principalSchema: "Reference",
            principalTable: "LaborResourceGroup",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
        table.ForeignKey(
            name: "FK_LaborVsGroup_LaborResource_LaborResourceId",
            column: x => x.LaborResourceId,
            principalSchema: "Reference",
            principalTable: "LaborResource",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    });

Why column & FK LaborResourceGroupsId created?

LaborResourceGroupsId = table.Column<int>(type: "int", nullable: false)

table.ForeignKey(
    name: "FK_LaborVsGroup_LaborResourceGroup_LaborResourceGroupsId",
    column: x => x.LaborResourceGroupsId,
    principalSchema: "Reference",
    principalTable: "LaborResourceGroup",
    principalColumn: "Id",
    onDelete: ReferentialAction.Cascade);

If I updated the join table to remove the virtual Navigation property, it works. So strange.

public class LaborVsGroup : IAuditableEntity, IWriteAccessEntity
{
    [Key]
    public int Id { get; set; }    
    public int LaborResourceId { get; set; }    
    public int LaborResourceGroupId { get; set; }
    public string CreatedBy { get; set; } = string.Empty;
    public DateTime CreatedOn { get; set; }
}

EF Version: 8.0.4

AndriySvyryd commented 5 months ago

In some cases we don't correctly find the related navigations on the join type, so you'd need to configure them explicitly:

            modelBuilder.Entity<LaborResourceGroup>()
                .HasMany(e => e.LaborResources)
                .WithMany(e => e.LaborResourceGroups)
                .UsingEntity<LaborVsGroup>(
                    jb => jb.HasOne(j => j.LaborResource).WithMany(),
                    jb => jb.HasOne(j => j.LaborResourceGroup).WithMany());