Внешний ключ как дискриминатор TPH в EF4 с использованием CTP4 Code First - PullRequest
3 голосов
/ 20 ноября 2010

Подводя итог моей модели:

  • Лицензия и сертификат являются потомками квалификации
  • Квалификация имеет одну и только одну профессию
  • Профессия является лицензированнойвид (тип 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:

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.Это означает, что тип не может быть одновременно дискриминатором и полем внешнего ключа.Попытка жестко закодировать Profession Type в методе HasConstraint для каждой сущности также не работает - это создает исключение.

Возможное решение - добавить суррогатный ключ в Profession, избавиться от свойства Type.в квалификации и замените его ProfessionId FK.Это устранит проблему избыточности, но также уничтожит TPH.По сути, дискриминатор переходит от квалификации к профессии.Проблема в том, что я не нашел способ сопоставить объекты License и Certificate.Может быть, я могу вместо этого сопоставить с видами?Но как мне это сделать в Code First?

Итак, теперь я столкнулся с рядом сомнительных решений.Есть предложения?

1 Ответ

1 голос
/ 20 ноября 2010

Мне удалось заставить его работать, изменив его на эту модель:

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 является избыточным для квалификации, и нет способа обойти его, поскольку, как вы сказали, 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

Еще один способ избежать СУХОГО состоит в том, чтобы превратить иерархию в 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

...