简体   繁体   English

使用CTP4 Code First在EF4中使用外键作为TPH鉴别器

[英]Foreign key as TPH discriminator in EF4 using CTP4 Code First

To summarize my model: 总结一下我的模型:

  • License and Certificate are children of Qualification 许可证和证书是资格证书的子女
  • A Qualification has one and only one Profession 资格证书只有一个职业
  • A Profession is either a licensed kind (type 1) or a certified kind (type 2) 专业是许可类型(类型1)或认证类型(类型2)

Requirement : Represent the relationships between business entities without introducing redundancy into database schema. 要求 :表示业务实体之间的关系,而不会在数据库模式中引入冗余。 Type of Qualification (license/certificate) must match type of Profession. 资格类型(许可证/证书)必须与职业类型相匹配。

Here is my simplified model as it currently stands -- I explain why this doesn't work below: 这是我目前的简化模型 - 我解释了为什么这不起作用如下:

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

Here's the simplified ModelBuilder: 这是简化的模型构建器:

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")

The reason this doesn't work is that EF4 does not allow FK properties to double as a TPH discriminator. 这不起作用的原因是EF4 不允许 FK属性加倍作为TPH鉴别器。 This means Type cannot be both a discriminator and a foreign key field. 这意味着Type不能同时是鉴别器和外键字段。 Trying to hard-code Profession Type in the HasConstraint method for each entity also doesn't work -- this generates an exception. 尝试在每个实体的HasConstraint方法中对专业类型进行硬编码也不起作用 - 这会生成异常。

A possible solution would be to add a surrogate key to Profession, get rid of Type property in Qualification and replace it with ProfessionId FK. 一个可能的解决方案是向Profession添加一个代理键,在Qualification中删除Type属性并将其替换为ProfessionId FK。 This would remove the redundancy concern, but also destroy TPH. 这将消除冗余问题,但也会破坏TPH。 In effect, the discriminator moves from Qualification to Profession. 实际上,鉴别者从资格化转向职业。 The problem here is that I haven't figured out a way to map License and Certificate objects. 这里的问题是我还没有想出一种映射许可证和证书对象的方法。 Maybe I can map to views instead? 也许我可以映射到视图而不是? But how do I do that in Code First? 但是我如何在Code First中做到这一点?

So, now I am faced with a number of unsavory choices. 所以,现在我面临着许多令人不快的选择。 Any suggestions? 有什么建议么?

I managed to make it works by changing it to this model: 我设法通过将其更改为此模型来使其工作:

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

However, as you can tell, ProfessionType is redundant on Qualification, and there is no way to workaround it since like you said, EF would not let you to reuse a discriminator as a FK which make sense since this rule: 但是,正如您所知,ProfessionType在Qualification上是多余的,并且没有办法解决它,因为就像你说的那样,EF不会让你重用一个鉴别器作为FK,因为这个规则是有意义的:

A Profession is either a licensed kind (type 1) or a certified kind (type 2) 专业是许可类型(类型1)或认证类型(类型2)

is something that EF is not aware of therefore it has to prevent it in order to protect the hierachy. 是EF不知道的东西,因此它必须防止它以保护层次。

Personally, I would design the object model as follows which I think is more clear and less redundant: 就个人而言,我会设计如下的对象模型,我认为它更清晰,更少冗余:

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

Which results to the following schema in DB: 这导致DB中的以下架构: 替代文字

Yet another way to avoid DRY would be to turn the hierarchy to be TPT instead of TPH: 另一种避免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");
    }
}


Which results to the following schema in DB: 这导致DB中的以下架构:

替代文字

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM