database-design - 使用 CTP4 Code First 的外键作为 EF4 中的 TPH 鉴别器

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

总结我的模型:

  • 执照和证书是资格的子项
  • 一项资格仅适用于一种职业
  • 职业可以是许可类型(类型 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 鉴别器。这意味着类型不能同时是鉴别符和外键字段。尝试在 HasConstraint 方法中为每个实体硬编码专业类型也不起作用 - 这会生成异常。

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

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

最佳答案

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

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,因为这条规则是有意义的:

A Profession is either a licensed kind (type 1) or a certified kind (type 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");
    }
}

这会在数据库中产生以下架构: alt text

避免 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");
    }
}


这会在数据库中产生以下架构:

alt text

关于database-design - 使用 CTP4 Code First 的外键作为 EF4 中的 TPH 鉴别器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4230007/

相关文章:

database - 数据库水平扩展和垂直扩展的区别

.net - Entity Framework 将某些 varchar 列的复合键错误地视为身份

mysql - 如何在生产中重命名表和某些列

php - 'other-table, column-dependent, dynamic table status'是什么?

sql - 字段内用逗号分隔的数据与新表

mysql - 如何设计一个 MySQL 表来跟踪每个 Assets 的状态以及每个旧状态?

javascript - 方法内的属性显示未定义但在方法外工作。如何修复它?

angular - 扩展 Angular Material2 MdTab 组件

c# - 覆盖子类中的通用 IEnumerable 接口(interface)

.net - EF : How do I decrease the very first DataContext initialization time