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

发布于 2024-10-03 15:20:46 字数 2217 浏览 4 评论 0原文

总结我的模型:

  • 许可证和证书是资格的子
  • 资格资格有一个且只有一个职业
  • 职业是许可类型(类型 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 不允许 FK 属性兼作 TPH 鉴别器。这意味着类型不能同时是鉴别符和外键字段。尝试在 HasConstraint 方法中为每个实体硬编码专业类型也不起作用 - 这会生成异常。

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

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. This means Type cannot be both a discriminator and a foreign key field. Trying to hard-code Profession Type in the HasConstraint method for each entity also doesn't work -- this generates an exception.

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. This would remove the redundancy concern, but also destroy 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?

So, now I am faced with a number of unsavory choices. Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

生生漫 2024-10-10 15:20:46

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

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

这会在数据库中产生以下模式:
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

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:

A Profession is either a licensed kind (type 1) or a certified kind (type 2)

is something that EF is not aware of therefore it has to prevent it in order to protect the hierachy.

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:
alt text

Yet another way to avoid DRY would be to turn the hierarchy to be TPT instead of 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:

alt text

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文