【问题标题】:Foreign key as TPH discriminator in EF4 using CTP4 Code FirstEF4 中使用 CTP4 Code First 作为 TPH 鉴别器的外键
【发布时间】:2010-11-19 22:38:28
【问题描述】:

总结一下我的模型:

  • License 和 Certificate 是 Qualification 的子代
  • 一个资格只有一个职业
  • 职业是许可类型(类型 1)或认证类型(类型 2)

要求:表示业务实体之间的关系,而不在数据库架构中引入冗余。资格类型(执照/证书)必须与专业类型相匹配。

这是我目前的简化模型 - 我在下面解释了为什么这不起作用:

Public Class Profession
    <Key()>
    <DataMember(Order:=0)>
    Public Property Type As Integer
    <Key()>
    <DataMember(Order:=1)>
    Public Property Code As String

    Public Property Title As String
End Class

Public Class Qualification
    Public Property Id As Integer
    Public Property PersonId As Integer
    Public Property Type As Integer
    Public Property ProfessionCode As String
    Public Overridable Property Person As Person
    Public Overridable Property Profession As Profession
End Class

Public Class License
    Inherits Qualification

    Public Property Number As String        
End Class

Public Class Certificate
    Inherits Qualification

    Public Property IssuerName As String    
End Class

这是简化的模型构建器:

modelBuilder.Entity(Of Qualification) _
    .Property(Function(q) q.ProfessionCode).IsRequired()

modelBuilder.Entity(Of Qualification) _
    .HasRequired(Of Profession)(Function(q) q.Profession) _
    .HasConstraint(Function(q, p) p.Type = q.Type AndAlso p.Code = q.ProfessionCode)

modelBuilder.Entity(Of Qualification) _
    .MapHierarchy() _
    .Case(Of Qualification)(Function(q) New With {
        q.Id,
        q.PersonId,
        q.ProfessionCode,
        .Type = 0) _
    .Case(Of License)(Function(q) New With {
        q.Number,
        .Type = 1}) _
    .Case(Of Certificate)(Function(q) New With {
        q.IssuerName,
        .Type = 2}) _
    .ToTable("dbo.Qualifications")

这不起作用的原因是 EF4 does not allow FK 属性兼作 TPH 鉴别器。这意味着 Type 不能既是鉴别符又是外键字段。尝试在 HasConstraint 方法中为每个实体硬编码职业类型也不起作用——这会产生异常。

一种可能的解决方案是向 Profession 添加代理键,去掉 Qualification 中的 Type 属性并将其替换为 ProfessionId FK。这将消除冗余问题,但也会破坏 TPH。实际上,鉴别器从资格转移到职业。这里的问题是我还没有找到映射许可证和证书对象的方法。也许我可以映射到视图?但是如何在 Code First 中做到这一点?

所以,现在我面临着许多令人讨厌的选择。有什么建议吗?

【问题讨论】:

    标签: database-design inheritance entity-framework-4 code-first ctp4


    【解决方案1】:

    我设法通过将其更改为这个模型使其工作:

    public class Profession {    
        [Key][DataMember(Order = 0)]    
        public int Type { get; set; }
        [Key][DataMember(Order = 1)]
        public string Code { get; set; }
        public string Title { get; set; }
    }
    
    public class Qualification {
        public int Id { get; set; }               
        [Required]
        public int ProfessionType { get; set; }
        [Required]
        public string ProfessionCode { get; set; }                
        [Required]
        public virtual Profession Profession { get; set; }
    }
    
    public class License : Qualification {
        public string Number { get; set; }  
    }
    
    public class Certificate : Qualification {
        public string IssuerName { get; set; }
    }
    
    class Context : DbContext {
        public DbSet<Qualification> Qualifications { get; set; }
        public DbSet<Profession> Professions { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder) {
            modelBuilder.Entity<Qualification>()
                .HasRequired<Profession>(q => q.Profession)
                .HasConstraint((q, p) => q.ProfessionCode == p.Code 
                                         && q.ProfessionType == p.Type);
    
            modelBuilder.Entity<Qualification>().MapHierarchy()
                .Case<Qualification>(q => new {
                    q.ProfessionCode,
                    q.ProfessionType,
                    q.Id,                    
                    Type = 0
                }).Case<License>(q => new {
                    q.Number,
                    Type = 1
                }).Case<Certificate>(q => new {
                    q.IssuerName,
                    Type = 2
                }).ToTable("Qualifications");
        }
    }
    

    但是,正如您所知,ProfessionType 在 Qualification 中是多余的,并且没有办法解决它,因为就像您说的那样,EF 不允许您将鉴别器重用为 FK,因为这条规则是有意义的:

    职业是许可类型(类型 1)或认证类型(类型 2)

    是 EF 不知道的,因此它必须阻止它以保护层次结构。

    就我个人而言,我会按如下方式设计对象模型,我认为这样更清晰,更少冗余:

    public class Profession {
        public int ProfessionId { get; set; }        
        public int Type { get; set; }
        public string Code { get; set; }
        public string Title { get; set; }
    }
    
    public class Qualification {
        public int Id { get; set; }
        public int ProfessionId { get; set; }                
        [Required]
        public virtual Profession Profession { get; set; }
    }
    
    public class License : Qualification {
        public string Number { get; set; }  
    }
    
    public class Certificate : Qualification {
        public string IssuerName { get; set; }
    }
    
    class Context : DbContext {
        public DbSet<Qualification> Qualifications { get; set; }
        public DbSet<Profession> Professions { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder) {
            modelBuilder.Entity<Qualification>()
                .HasRequired<Profession>(q => q.Profession)
                .HasConstraint((q, p) => q.ProfessionId == p.ProfessionId);
    
            modelBuilder.Entity<Qualification>().MapHierarchy()
                .Case<Qualification>(q => new {
                    q.ProfessionId,                   
                    q.Id,                    
                    Type = 0
                })
                .Case<License>(q => new {
                    q.Number,
                    Type = 1
                })
                .Case<Certificate>(q => new {
                    q.IssuerName,
                    Type = 2
                })
                .ToTable("Qualifications");
        }
    }
    

    这会导致 DB 中的以下架构:

    另一种避免 DRY 的方法是将层次结构转换为 TPT 而不是 TPH:

    public class Profession {
        [Key]
        [DataMember(Order = 0)]
        public int Type { get; set; }
        [Key]
        [DataMember(Order = 1)]
        public string Code { get; set; }
        public string Title { get; set; }
    }
    
    public class Qualification {
        public int Id { get; set; }
        [Required]
        public int ProfessionType { get; set; }
        [Required]
        public string ProfessionCode { get; set; }
        [Required]
        public virtual Profession Profession { get; set; }
    }
    
    public class License : Qualification {
        public string Number { get; set; }
    }
    
    public class Certificate : Qualification {
        public string IssuerName { get; set; }
    }
    
    class Context : DbContext 
    {
        public DbSet<Qualification> Qualifications { get; set; }
        public DbSet<Profession> Professions { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder) {
            modelBuilder.Entity<Qualification>()
                .HasRequired<Profession>(q => q.Profession)
                .HasConstraint((q, p) => q.ProfessionCode == p.Code
                                         && q.ProfessionType == p.Type);
    
            modelBuilder.Entity<Qualification>().MapHierarchy(q => new 
            {
                q.Id,
                q.ProfessionCode,
                q.ProfessionType,
            })
            .ToTable("Qualifications");
    
            modelBuilder.Entity<License>().MapHierarchy(l => new 
            {
                l.Id,
                l.Number
            })
            .ToTable("Licenses");
    
            modelBuilder.Entity<Certificate>().MapHierarchy(c => new 
            {
                c.Id,
                c.IssuerName
            })
            .ToTable("Certificates");
        }
    }
    


    这导致 DB 中的以下模式:

    【讨论】:

    • @Morteza,感谢您的贡献。我希望可能有更好的方法不违反 DRY。如您所知,Type 在此解决方案中是多余的。你会尝试通过业务规则来控制它吗?
    • 通过通过业务规则控制,您的意思是根本没有鉴别器列,只需查看 Qualification 表中的 ProfessionType 并找出您正在处理的对象类型,对吗?
    • 我添加了另一种避免 DRY 的方法:TPT 而不是 TPH。
    • 不幸的是,这仍然会产生数据库异常。例如,在没有任何业务规则的情况下,我可以创建一个 License 对象并将其 ProfessionType 设置为 2(即已认证),因为 ProfessionType 不再是鉴别器。我可以通过在 POCO 级别或更高级别添加验证逻辑来​​缓解这种情况。这样的规则会比我在第一个建议的解决方案中必须做的更好:查询 Profession 表以验证 ProfessionId。当然,这仍然不能防止直接写入数据库。阿格!
    • 我在这里追逐一个不可能的目标吗?我不禁想到这种场景必须有一个最佳实践,因为这不仅仅是代码优先问题,而是一般 EF 的限制。
    猜你喜欢
    • 1970-01-01
    • 2011-05-06
    • 2011-05-03
    • 2011-07-13
    • 2012-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    相关资源
    最近更新 更多