2016-11-14 4 views
0

Мне нужно преобразовать некоторую существующую БД в новое приложение, используя EF6 с подходом CodeFirst. Я борюсь с соглашениями об отображении в наследовании. Существует минимальный (не) рабочий пример:Entity framework 6 настраиваемых ключей для наследования типа таблицы за тип

Предположим, у меня есть две таблицы: родитель и ребенок.

CREATE TABLE A_PARENT (
    A_PAR_ParentId UNIQUEIDENTIFIER PRIMARY KEY, 
    A_PAR_data VARCHAR(255) 
) 

CREATE TABLE B_CHILD (
    B_CHL_ChildId UNIQUEIDENTIFIER PRIMARY KEY FOREIGN KEY REFERENCES A_PARENT(A_PAR_ParentId), 
    B_CHL_childData VARCHAR(255) 
) 

Я успешно решала таблицу prefices вы можете видеть, такое «A_PAR» с использованием пользовательских атрибутов. Я считаю, что EF точно знает, какое свойство принадлежит тому столбцу, а какой - первичный ключ. Все работает отлично, за исключением наследования. Потому что, когда я пытаюсь получить все дети, я в конечном итоге с ошибкой SQL, так как EF производит запрос следующим образом:

SELECT 
    '0X0X' AS [C1], 
    [Extent1].[A_PAR_ParentId] AS [A_PAR_ParentId], 
    [Extent1].[A_PAR_Data] AS [A_PAR_Data], 
    [Extent2].[B_CHL_ChildId] AS [B_CHL_ChildId], 
    [Extent2].[B_CHL_ChildData] AS [B_CHL_ChildData] 
    FROM [dbo].[A_PARENT] AS [Extent1] 
    INNER JOIN [dbo].[B_CHILD] AS [Extent2] ON [Extent1].[A_PAR_ParentId] = [Extent2].[A_PAR_ParentId] 

только неправильно, что в запросе является Предикат - там нет такого столбец A_PAR_ParentId в таблице B_CHILD.

Как я могу заставить EntityFramework использовать первичный ключ сущности для использования в качестве внешнего ключа при построении цепочки наследования? Я ищу какое-то общее решение, основанное на соглашениях, потому что все таблицы в БД используют этот шаблон (если тип/таблица наследуется => первичный ключ является внешним ключом к основному ключу родителя, и нет составных клавиш в все). Возможно, я ищу способ сообщить EF, что ПК также является FK, но без свойства навигации.

--edit: больше кода

модель довольно проста:

[ModulePrefix("A"), TablePrefix("PAR")] 
    public class Parent 
    { 
     public Guid ParentId { get; set; } 
     public string Data { get; set; } 
    } 

    [ModulePrefix("B"), TablePrefix("CHL")] 
    public class Child : Parent 
    { 
     public Guid ChildId { get; set; } 
     public string ChildData { get; set; } 
    } 

И конфигурация:

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      base.OnModelCreating(modelBuilder); 
      modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 

      modelBuilder.Types() 
       .Where(type => !type.GetCustomAttributes(false).OfType<TableAttribute>().Any() && type.GetCustomAttributes(false).OfType<ModulePrefixAttribute>().Any()) 
       .Configure(config => config.ToTable(
        ComposeDbName(GetModulePrefix(config.ClrType), 
        CamelCaseToUnderscore(GetClassName(config.ClrType)).ToUpper()) 
        )); 

      modelBuilder.Properties() 
       .Where(property => property.DeclaringType.GetCustomAttributes(false).OfType<TablePrefixAttribute>().Any() && property.DeclaringType == property.ReflectedType) 
       .Configure(config => config.HasColumnName(ComposeDbName(
        GetModulePrefix(config.ClrPropertyInfo.DeclaringType), 
        GetTablePrefix(config.ClrPropertyInfo.DeclaringType), 
        config.ClrPropertyInfo.Name 
        ))); 
      modelBuilder.Properties() 
       .Where(property => property.Name == property.DeclaringType.Name + "Id" && property.ReflectedType == property.DeclaringType) 
       .Configure(config => config.IsKey()); 

      modelBuilder.Properties() 
       .Where(property => property.PropertyType.IsClass || Nullable.GetUnderlyingType(property.PropertyType) != null) 
       .Configure(config => config.IsOptional()); 
     } 

Есть некоторые более частные методы играют с столбцами/именами таблиц , Я думаю, что это не важно на данный момент, так как имена столбцов/таблиц выглядят хорошо разрешенными.

+0

Давайте рассмотрим первую модель/конфигурацию кода, связанную с приведенными выше таблицами. –

+0

Я только что добавил код к вопросу. –

+0

Поле 'Child.ChildId' вызывает проблему. –

ответ

0

если тип/таблица наследуется => первичный ключ является внешним ключом к родительским первичный ключ

Это точно, как работает EF TPT отображение наследования, поэтому никакого конфликта здесь.

Проблема ChildId Недвижимость в Child класс. Так как Child наследует Parent, он также наследует свойство ParentId, что приводит к неправильному отображению TPT. Вы должны удалить его и использовать свойство, основанное на базовом классе, как PK (и FK), просто укажите его другое имя в производной таблице.

Я предлагаю вам использовать Id в качестве имени для свойства PK, чтобы можно было легко связать другое имя по соглашению.

Так вот как скорректированная модель будет выглядеть следующим образом:

[ModulePrefix("A"), TablePrefix("PAR")] 
public class Parent 
{ 
    public Guid Id { get; set; } 
    public string Data { get; set; } 
} 

[ModulePrefix("B"), TablePrefix("CHL")] 
public class Child : Parent 
{ 
    public string ChildData { get; set; } 
} 

и конфигурации на основе этой конвенции:

modelBuilder.Types() 
    .Where(type => !type.GetCustomAttributes(false).OfType<TableAttribute>().Any() && type.GetCustomAttributes(false).OfType<ModulePrefixAttribute>().Any()) 
    .Configure(config => config.ToTable(GetTableName(config.ClrType))); 

modelBuilder.Properties() 
    .Where(property => property.Name != "Id" && property.DeclaringType.GetCustomAttributes(false).OfType<TablePrefixAttribute>().Any() && property.DeclaringType == property.ReflectedType) 
    .Configure(config => config.HasColumnName(GetColumnName(config.ClrPropertyInfo.DeclaringType, config.ClrPropertyInfo.Name))); 

modelBuilder.Properties() 
    .Where(property => property.Name == "Id" && property.DeclaringType.GetCustomAttributes(false).OfType<TablePrefixAttribute>().Any()) 
    .Configure(config => config.IsKey().HasColumnName(GetColumnName(config.ClrPropertyInfo.ReflectedType, "Id"))); 

, который использует два новых частных помощников в дополнение к твоему:

static string GetTableName(Type entityType) 
{ 
    return ComposeDbName(
     GetModulePrefix(entityType), 
     CamelCaseToUnderscore(GetClassName(entityType)).ToUpper() 
    ); 
} 

static string GetColumnName(Type entityType, string propertyName) 
{ 
    return ComposeDbName(
     GetModulePrefix(entityType), 
     GetTablePrefix(entityType), 
     propertyName == "Id" ? GetClassName(entityType) + "Id" : propertyName 
    ); 
} 

Что он делает, так это условно эквивалентно:

modelBuilder.Entity<Parent>().ToTable("A_PARENT"); 
modelBuilder.Entity<Parent>().Property(e => e.Id).HasColumnName("A_PAR_ParentId"); 
modelBuilder.Entity<Parent>().HasKey(e => e.Id); 
modelBuilder.Entity<Parent>().Property(e => e.Data).HasColumnName("A_PAR_Data"); 

modelBuilder.Entity<Child>().ToTable("B_CHILD"); 
modelBuilder.Entity<Child>().Property(e => e.Id).HasColumnName("B_CHL_ChildId"); 
modelBuilder.Entity<Child>().HasKey(e => e.Id); 
modelBuilder.Entity<Child>().Property(e => e.ChildData).HasColumnName("B_CHL_ChildData"); 
+0

Не предполагает ли, что имя первичного/внешнего ключа в дочерней таблице названо так же как первичный ключ родителя? –

+0

Предлагаемая конфигурация генерирует точные имена столбцов, как показано в таблицах базы данных - 'A_PAR_ParentId' и' B_CHL_ChildId' –

+0

. Предполагается использовать одноименное имя свойства PK ** ** (в классах) и сопоставить его с другим именем столбца в каждая таблица основана на согласии. –