【问题标题】:Map collection using foreign key with constant value使用具有恒定值的外键的地图集合
【发布时间】:2020-05-20 23:27:02
【问题描述】:

我设计的表格可以包含来自多个表格(StudentsTeachers)的记录的参数(Parameters)。

作为参数表上的外键,我使用一个枚举(StudentTeacher),按列与多个表中的主键配对。

如何用参数表中的记录填充对象集合(Student.ParametersTeacher.Parameters)?

我会尝试以学校的简单例子来解释。

EF Core Code First 数据库上下文:

class Student
{
   [Key]
   public Guid Id { get; set; }
   public IList<Parameter> Parameters { get; set; } 
}
class Teacher
{
   [Key]
   public Guid Id { get; set; }
   public IList<Parameter> Parameters { get; set; } 
}
enum EntityType
{
   Student,
   Teacher
}
class Parameter
{
   [Key]
   public Guid Id { get; set; }
   public EntityType EntityType { get; set; }
   public Guid EntityId { get; set; }
   public string Value { get; set; }
}
class SchoolDbContext : DbContext
{
   public DbSet<Student> Students { get; set; }
   public DbSet<Teacher> Teachers { get; set; }
   public DbSet<Parameter> Parameters { get; set; }

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      modelBuilder.Entity<Parameter>()
          .HasKey(prm => new { prm.EntityType, prm.EntityId });
   }
}

为了填充 Teacher 对象的 Parameters 集合(还有 Student.Parameters),我希望应用程序执行如下 SQL:

SELECT prm.*
  FROM Teachers tch
  LEFT JOIN Parameters prm
    ON prm.EntityType = 'Teacher'
   AND prm.EntityId = tch.Id
 WHERE tch.Id = @id

像这样使用它(如果可能的话,也用于级联删除):

Guid studentId = .....;
var student = SchoolDbContext.Students
   .Include(s => s.Parameters)
   .Find(studentId);

【问题讨论】:

    标签: c# entity-framework .net-core ef-code-first ef-core-3.0


    【解决方案1】:

    您可以这样做并使用每个层次结构的表策略吗? https://entityframework.net/tph

    enum EntityType
    {
       Student,
       Teacher
    }
    class Person
    {
        public Guid Id {get;set;}
        public EntityType EntityType { get; set; }
        public IList<Parameter> Parameters { get; set; } 
    }
    class Student : Person
    {
    
    }
    class Teacher : Person
    {
    
    }
    class Parameter
    {
       [Key]
       public Guid Id { get; set; }
       public Guid PersonId { get; set; }
       public string Value { get; set; }
    }
    

    【讨论】:

    • 这很有趣,并且在类从基类继承的情况下肯定可以工作。使用实体类型枚举也没有意义(我认为两个表将共享相同的主键序列)。但是当实体没有相同的基类时,这将失败。
    【解决方案2】:

    我有与原始海报相同的需求和类似的方案。我能够让它与上面提到的 TPH 策略一起工作。然后我有一个业务要求,即这种关系是可编辑的,EF Core 不允许您编辑作为鉴别器的属性。我找到的最佳解决方案是在存储 EntityType 的相关实体上创建一个计算列(始终返回与该 EntityType 相关的值) 不是最好的,但到目前为止是有效的。我尝试对 HasPrinicipalKey 的 EntityType 使用固定值,但收到 EF Core 不允许的错误。

            modelBuilder.Entity<ServiceNote>()
                .HasOne(sn => sn.Policy)
                .WithMany()
                .HasForeignKey(sn => new { sn.EntityId, sn.EntityType })
                .HasPrincipalKey(policy => new { EntityId = policy.Id, EntityType = policy.ServiceNoteEntityType });
    
            modelBuilder.Entity<ServiceNote>()
                .HasOne(sn => sn.Quote)
                .WithMany()
                .HasForeignKey(sn => new { sn.EntityId, sn.EntityType })
                .HasPrincipalKey(quote => new { EntityId = quote.Id, EntityType = quote.ServiceNoteEntityType });
    
            modelBuilder.Entity<ServiceNote>()
                .HasOne(sn => sn.Agency)
                .WithMany()
                .HasForeignKey(sn => new { sn.EntityId, sn.EntityType })
                .HasPrincipalKey(agency => new { EntityId = agency.Id, EntityType = agency.ServiceNoteEntityType });
    
            modelBuilder.Entity<ServiceNote>()
                .HasOne(sn => sn.Client)
                .WithMany()
                .HasForeignKey(sn => new { sn.EntityId, sn.EntityType })
                .HasPrincipalKey(client => new { EntityId = client.Id, EntityType = client.ServiceNoteEntityType });
    

    【讨论】:

      【解决方案3】:

      你可以这样使用

      class Student
      {
         [Key]
         public Guid Id { get; set; }
      
         // add virtual
         public virtual IList<Parameter> Parameters { get; set; } 
      }
      class Teacher
      {
         [Key]
         public Guid Id { get; set; }
         // add virtual
         public virtual IList<Parameter> Parameters { get; set; } 
      }
      enum EntityType
      {
         Student,
         Teacher
      }
      class Parameter
      {
         [Key]
         public Guid Id { get; set; }
         public EntityType EntityType { get; set; }
         public Guid EntityId { get; set; }
         public string Value { get; set; }
      
         [ForeignKey("Student")]
         public Guid StudentId { get; set; }
         [ForeignKey("Teacher")]
         public Guid TeacherId { get; set; }
      }
      

      希望对你有帮助

      【讨论】:

      • 这将在数据库中为学生和教师创建列。按照设计,应该只有两列用于绑定。我有超过 2 个引用参数表的表,我不想为每个表创建 %tablename%Id。
      • @Rincew1nd “我不想为每个表创建 %tablename%Id。” - 但是您确实应该这样做,否则如果您不希望 TPH 和所有“参数化”的相同基类,则无法维护数据库的引用完整性。另请注意,此答案中的EntityTypeEntityId 完全是多余的,应将其删除,但如果不能选择TPH,则此解决方案是最好的解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-06
      • 2021-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多