EF Core 一对多对多对一关系

EF Core one to many to many to one relationship

我已经使用数据库很长时间了,但对 Entity Framework 还是个新手。我处理编程和数据库开发这两个方面。作为数据库开发人员,我尽量保持它的干净,所以我想出的这个结构对我来说效果很好,但我不确定 Entity Framework 是否支持它,因为我已经尝试了几天,使用不同的场景, Data Annotations 以及 Fluent API 但无法让它工作。

我想做的可能有点不合常规,但我想避免的是必须为每个区域复制一个文件 table 因此我定义了 1 个文件 table可以使用关系由多个区域使用。因此,我所拥有的是:一个 [公司、员工或项目] 可以有多个文件(一对多)。同样,文件 table 可以由任何区域获取(多对多,在这种情况下,它不是数据而是结构,希望这是有意义的)。文件记录仅与1个区域[公司、员工或项目]相关(多对一)。

此方法的明显优点是我可以避免管理 3 个文件 table,但它并没有就此结束。正如您从 FileAccess table 中看到的那样,而不是在这里有多个 table 或多个字段来表示指向多个 table 的指针,我只需要管理 1 table 用于文件访问。关键在 RelationTable 和 RelationId 而不是特定的 File.Id.

下面是我要完成的结构的简化示例。可以在 Entity Framework 内完成吗?

  public class Company
  {
    public Guid Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<File> Files { get; set; }
  }

  public class Employee
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<File> Files { get; set; }
  }

  public class Project
  {
    public int Id { get; set; }
    public Guid? CompanyId { get; set; }
    public string ProjectNo {get; set; }
    public virtual ICollection<File> Files { get; set; }
  }

  public class File
  {
    public int Id { get; set; }
    public Int16 RelationTable { get; set; } 0=Company, 1=Employee, 2=Project
    public string RelationId { get; set; } Company.Id, Employee.Id, Project.Id
    public string FileName { get; set; }
  }

  public class FileAccess
  {
    public int Id { get; set; }
    public int EmployeeId { get; set; }
    public Int16 RelationTable { get; set; } 0=Company, 1=Employee, 2=Project
    public string RelationId { get; set; } Company.Id, Employee.Id, Project.Id
    public string AccessType
  }

正如 Ivan 指出的那样,由于外键限制,EF 不支持这一点,但我能够想出一个可行的解决方案。但是,我必须警告您,我才刚刚进入 EF 的第 3 周,所以我不知道这可能会导致什么后果,但对于那些可能感兴趣的人,这就是我所做的。

事实证明(通过反复试验),EF 只需要 OnModelCreating 来连接对象之间的关系,它实际上并不需要创建 FK,因此我这样定义了关系:

  modelBuilder.Entity<File>()
  .HasIndex(k => new { k.RelationTable, k.RelationId }); //for performance

  modelBuilder.Entity<FileAccess>()
    .HasMany(fa => fa.Files)
    .WithOne(f => f.FileAccess)
    .HasForeignKey(k => new { k.RelationTable, k.RelationId })
    .HasPrincipalKey(k => new { k.RelationTable, k.RelationId });

//Using enumerations to control 
    relationships and adding PERSISTED so it doesn't need to be maintained plus it 
    won't break the Add-Migration with the "non persistent error"

  modelBuilder.Entity<Project>()
    .Property(f => f.RelationTable)
    .HasComputedColumnSql((int)NTGE.Database.Shared.eFileRelTable.Projects + " PERSISTED") //This injects the value so we don't have to store it
    .HasDefaultValue(123); //This doesn't really matter, we just need it so EF doesn't try to insert a value when saving, which will cause an error

  modelBuilder.Entity<Project>()
    .HasMany(p => p.Files)
    .WithOne(f => f.Project)
    .HasForeignKey(k => new { k.RelationTable, k.RelationId })
    .HasPrincipalKey(k => new { k.RelationTable, k.Id });

当您添加上述代码和 运行 添加迁移时,它会导致添加以下代码,这将破坏更新数据库命令,因此您需要将其注释掉在向上函数中。

        //migrationBuilder.AddForeignKey(
        //    name: "FK_Files_Projects_RelationTable_RelationId",
        //    table: "Files",
        //    columns: new[] { "RelationTable", "RelationId" },
        //    principalTable: "Projects",
        //    principalColumns: new[] { "RelationTable", "Id" },
        //    onDelete: ReferentialAction.Cascade);

        //migrationBuilder.AddForeignKey(
        //    name: "FK_Files_FileAccess_RelationTable_RelationId",
        //    table: "Files",
        //    columns: new[] { "RelationTable", "RelationId" },
        //    principalTable: "FileAccess",
        //    principalColumns: new[] { "RelationTable", "RelationId" },
        //    onDelete: ReferentialAction.Cascade);

您需要对“向下”功能执行相同操作,否则您将无法回滚更改。

        //migrationBuilder.DropForeignKey(
        //    name: "FK_Files_Projects_RelationTable_RelationId",
        //    table: "Files");

        //migrationBuilder.DropForeignKey(
        //    name: "FK_Files_FileAccess_RelationTable_RelationId",
        //    table: "Files");

现在您可以执行更新数据库,它应该 运行 就好了。 运行 该应用程序也运行良好。我能够使用 EF 方法获取带有关联文件的项目,并使用 FileAccess 对象。但是,请记住,这是一种 hack,未来版本的 EF 可能不支持它。干杯!