You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何EF迁移时自动生成EmployeesProjects表?已自定义关联表

问题:EF Core迁移自动生成多余的多对多关联表

我有Employees和Projects两个实体,二者为多对多关系。我已自定义ProjectEmployee作为关联表,但执行Entity Framework迁移时,系统仍自动生成EmployeesProjects表。手动删除迁移代码中的该表创建逻辑后,再次执行迁移仍会生成它,排查所有配置文件未发现问题,请问原因及解决办法?


实体代码

Employees.cs

using Day1.Model;
using System.ComponentModel.DataAnnotations;

public class Employees
{
    [Required]
    public int Id { get; set; }
    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
    public int DepartmentId { get; set; }
    public DateTime JoinedDate { get; set; }
    public Salaries Salary { get; set; }
    public IList<Projects> Projects { get; set; }
    public Departments Department { get; set; }
    public IList<ProjectEmployee> ProjectEmployees { get; set; }
}

Projects.cs

using Day1.Model;
using System.ComponentModel.DataAnnotations;

public class Projects
{
    [Required]
    public int Id { get; set; }
    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
    public IList<Employees> Employees { get; set; }
    public IList<ProjectEmployee> ProjectEmployees { get; set; }
}

ProjectEmployee.cs

namespace Day1.Model
{
    public class ProjectEmployee
    {
        public int ProjectId { get; set; }
        public Projects Project { get; set; }
        public int EmployeeId { get; set; }
        public Employees Employee { get; set; }
        public bool Enable { get; set; }
    }
}

生成的迁移代码

using System;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional

namespace Day1.Migrations
{
    /// <inheritdoc />
    public partial class InitialCreate : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.CreateTable(
                name: "Departments",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Departments", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "Projects",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Projects", x => x.Id);
                });

            migrationBuilder.CreateTable(
                name: "Employees",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
                    DepartmentId = table.Column<int>(type: "int", nullable: false),
                    JoinedDate = table.Column<DateTime>(type: "datetime2", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Employees", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Employees_Departments_DepartmentId",
                        column: x => x.DepartmentId,
                        principalTable: "Departments",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "EmployeesProjects",
                columns: table => new
                {
                    EmployeesId = table.Column<int>(type: "int", nullable: false),
                    ProjectsId = table.Column<int>(type: "int", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_EmployeesProjects", x => new { x.EmployeesId, x.ProjectsId });
                    table.ForeignKey(
                        name: "FK_EmployeesProjects_Employees_EmployeesId",
                        column: x => x.EmployeesId,
                        principalTable: "Employees",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_EmployeesProjects_Projects_ProjectsId",
                        column: x => x.ProjectsId,
                        principalTable: "Projects",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "ProjectEmployees",
                columns: table => new
                {
                    ProjectId = table.Column<int>(type: "int", nullable: false),
                    EmployeeId = table.Column<int>(type: "int", nullable: false),
                    Enable = table.Column<bool>(type: "bit", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_ProjectEmployees", x => new { x.ProjectId, x.EmployeeId });
                    table.ForeignKey(
                        name: "FK_ProjectEmployees_Employees_EmployeeId",
                        column: x => x.EmployeeId,
                        principalTable: "Employees",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                    table.ForeignKey(
                        name: "FK_ProjectEmployees_Projects_ProjectId",
                        column: x => x.ProjectId,
                        principalTable: "Projects",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.CreateTable(
                name: "Salaries",
                columns: table => new
                {
                    Id = table.Column<int>(type: "int", nullable: false)
                        .Annotation("SqlServer:Identity", "1, 1"),
                    EmployeeId = table.Column<int>(type: "int", nullable: false),
                    Salary = table.Column<int>(type: "int", nullable: false)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Salaries", x => x.Id);
                    table.ForeignKey(
                        name: "FK_Salaries_Employees_EmployeeId",
                        column: x => x.EmployeeId,
                        principalTable: "Employees",
                        principalColumn: "Id",
                        onDelete: ReferentialAction.Cascade);
                });

            migrationBuilder.InsertData(
                table: "Departments",
                columns: new[] { "Id", "Name" },
                values: new object[,]
                {
                    { 1, "Software Development" },
                    { 2, "Finance" },
                    { 3, "Accountant" },
                    { 4, "HR" }
                });

            migrationBuilder.CreateIndex(
                name: "IX_Employees_DepartmentId",
                table: "Employees",
                column: "DepartmentId");

            migrationBuilder.CreateIndex(
                name: "IX_EmployeesProjects_ProjectsId",
                table: "EmployeesProjects",
                column: "ProjectsId");

            migrationBuilder.CreateIndex(
                name: "IX_ProjectEmployees_EmployeeId",
                table: "ProjectEmployees",
                column: "EmployeeId");

            migrationBuilder.CreateIndex(
                name: "IX_Salaries_EmployeeId",
                table: "Salaries",
                column: "EmployeeId",
                unique: true);
        }

        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropTable(
                name: "EmployeesProjects");

            migrationBuilder.DropTable(
                name: "ProjectEmployees");

            migrationBuilder.DropTable(
                name: "Salaries");

            migrationBuilder.DropTable(
                name: "Projects");

            migrationBuilder.DropTable(
                name: "Employees");

            migrationBuilder.DropTable(
                name: "Departments");
        }
    }
}

原因分析

你同时在Employees和Projects实体中定义了两种关联逻辑:

  1. 直接的多对多导航属性:Employees.Projects和Projects.Employees
  2. 基于自定义关联表的导航属性:Employees.ProjectEmployees和Projects.ProjectEmployees

EF Core会将第一种直接的多对多关系识别为需要自动生成默认关联表,第二种则对应你自定义的ProjectEmployee表,因此会同时生成两个关联表。

解决办法

方法一:移除直接的多对多导航属性

删除Employees中的public IList<Projects> Projects { get; set; }和Projects中的public IList<Employees> Employees { get; set; },只保留自定义关联表的导航属性。

修改后的Employees.cs:

using Day1.Model;
using System.ComponentModel.DataAnnotations;

public class Employees
{
    [Required]
    public int Id { get; set; }
    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
    public int DepartmentId { get; set; }
    public DateTime JoinedDate { get; set; }
    public Salaries Salary { get; set; }
    public Departments Department { get; set; }
    public IList<ProjectEmployee> ProjectEmployees { get; set; }
}

修改后的Projects.cs:

using Day1.Model;
using System.ComponentModel.DataAnnotations;

public class Projects
{
    [Required]
    public int Id { get; set; }
    [Required]
    [MaxLength(50)]
    public string Name { get; set; }
    public IList<ProjectEmployee> ProjectEmployees { get; set; }
}

方法二:通过Fluent API显式配置多对多关系

如果需要保留直接的多对多导航属性,可以在DbContext的OnModelCreating方法中,显式指定使用自定义的ProjectEmployee表作为关联表,覆盖EF Core的默认行为:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employees>()
        .HasMany(e => e.Projects)
        .WithMany(p => p.Employees)
        .UsingEntity<ProjectEmployee>(
            j => j.HasOne(pe => pe.Project).WithMany(p => p.ProjectEmployees),
            j => j.HasOne(pe => pe.Employee).WithMany(e => e.ProjectEmployees));
}

后续操作

修改完成后,需要删除之前的迁移文件并重新生成:

  1. 删除Migrations文件夹下的所有迁移文件(若已执行过迁移,需同时删除数据库中__EFMigrationsHistory表的对应记录)
  2. 执行命令生成新迁移:Add-Migration InitialCreate(Package Manager Console)或dotnet ef migrations add InitialCreate(CLI)
  3. 执行迁移更新数据库:Update-Database(Package Manager Console)或dotnet ef database update(CLI)

内容的提问来源于stack exchange,提问作者Phương Nguyễn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 07:55:54