EF 6 - Кодирование первого недопустимого отношения "один к одному" внешнего ключа

ПРЕДПОСЫЛКИ КОНСТРУКЦИИ:

Я пытаюсь создать сопоставления EF6 с кодовым кодом для следующей структуры базы данных:

Конструкция базы данных такова: Вместо того, чтобы иметь "CustomerID" как внешний ключ для всех связанных лиц (занятость, расходы, доход и т.д.), у нас есть таблица CustomerRelationship, в которой будет идентификатор CustomerID, а затем столбец "Связанный идентификатор", который будет содержать ключ связанного объекта. Например, скажем, я добавляю запись занятости для CustomerID = 1, тогда произойдет следующее:

  • Создайте запись в CustomerRelationship, установив CustomerID = 1 RelatedID = {новый автогенерированный идентификатор занятости, скажем 5} CustomerRelationshipTypeID = 55 (идентификатор в таблице поиска, в котором указано, что эта запись относится к типу занятости)

  • Создать запись в таблице занятости (EmploymentID = 5)

Вышеупомянутая структура будет работать для всех объектов, привязанных к клиенту.

DB Diagram У меня есть сопоставления отношений, работающие для Занятости, вот мои классы:

public abstract class EntityBase : IEntity
{
    #region IEntity Members
    public int Id { get; set; }

    public DateTime CreatedDate { get; set; }

    public int CreatedUserId { get; set; }

    public int CreatedSource { get; set; }

    public DateTime ModifiedDate { get; set; }

    public int ModifiedUserId { get; set; }

    public int? DataMigrationId { get; set; } 

    public bool IsActive { get; set; }                                    
    #endregion
}


public class Employment : EntityBase
{
    // ... all properties here.. removed most so easier to read
    public int EmploymentTypeId { get; set; }    

    **public virtual ICollection<EmploymentRelationship> EmploymentRelationships { get; set; }**
}

    public EmploymentMap()
    {
        this.HasKey(t => t.Id);
        ToTable("tblEmployment");
        Property(t => t.Id).HasColumnName("EmploymentID");    
        // Mapping for all properties follow       
    }

public abstract partial class CustomerRelationship : EntityBase
{
    public int CustomerId { get; set; }

    public decimal? PercentageShare { get; set; }

    public int CustomerRelationshipTypeId { get; set; }

    public int RelatedId { get; set; }
}

public class EmploymentRelationship : CustomerRelationship
{       
    public virtual Employment Employment { get; set; }
}

    public EmploymentRelationshipMap()
    {
        this.HasKey(t => t.Id);

        Map<EmploymentRelationship>(m =>
        {
            m.Requires("CustomerRelationshipTypeID").HasValue(55).IsRequired(); // Define lookup value for type of employment
            m.ToTable("tblCustomerRelationship");
        });

        Property(t => t.Id).HasColumnName("CustomerRelationshipID");
        Property(t => t.CustomerId).HasColumnName("CustomerID");
        Property(t => t.RelatedId).HasColumnName("RelatedID");

        HasRequired(t => t.Employment)
            .WithMany(t => t.EmploymentRelationships)
            .HasForeignKey(t => t.RelatedId);
    }

public class Customer : EntityBase
{
    // Customer Properties...
    public Customer()
    {
        EmploymentRelationships = new List<EmploymentRelationship>();
    }

    public virtual ICollection<EmploymentRelationship> EmploymentRelationships { get; set; }
}

    public CustomerMap()
    {
        this.HasKey(t => t.Id);

        ToTable("tblCustomer");

        Property(t => t.Id).HasColumnName("CustomerID");
    }


public class CustomerContext 
{
    public CustomerContext()
        : base(SymmetryCopy.context_connectionstring_main)
    {
    }

    public virtual DbSet<Customer> Customers { get; set; }
    public virtual DbSet<Employment> Employments { get; set; }

    #region Customer Relationship entity mappings
    public virtual DbSet<EmploymentRelationship> EmploymentRelationships { get; set; }
    #endregion

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new CustomerMap());
        modelBuilder.Configurations.Add(new EmploymentMap());

        #region Customer Relationship entity mappings
        modelBuilder.Configurations.Add(new EmploymentRelationshipMap());
        #endregion
    }
}

CustomerRepo для запроса контекста и результатов возврата:

public class CustomerRepository : BaseRepository<Customer, CustomerContext>, ICustomerRepository
{
    public CustomerRepository() : 
        base(new CustomerContext())
    {

    }

    public async Task<List<Employment>> GetEmployments(int customerId)
    {
        List<Employment> employments = new List<Employment>();
        using (var context = new CustomerContext())
        {
            var employmentRelationships = context.EmploymentRelationships.Where(l => l.CustomerId == customerId).ToList();
            employments = employmentRelationships.Select(x => x.Employment).ToList();
        }
        return employments;
    }
}

Вышеуказанный метод GetEmployments затем возвращает все записи, соответствующие идентификатору CustomerID с CustomerRelationshipTypeID = 55 (значение Key для Employers). Смотрите ниже.

введите описание изображения здесь

Теперь, чтобы узнать мои актуальные вопросы:

Когда я пытаюсь подключить другой тип Entity, т.е. Expense, следуя такому же подходу, что и занятость, создавая Expense.cs, ExpenseMap.cs, ExpenseRelationship.cs, ExpenseRelationshipMap.cs, имеющие следующее значение в ExpenseRElationshipMap.cs

public class ExpenseRelationshipMap
{
    public ExpenseRelationshipMap()
    {
        HasKey(t => t.Id);

        Map<ExpenseRelationship>(m =>
        {
            m.Requires("CustomerRelationshipTypeID").HasValue(60).IsRequired();
            m.ToTable("tblCustomerRelationship");  // Define lookup value for type of Expense
        });

        Property(t => t.Id).HasColumnName("CustomerRelationshipID");
        Property(t => t.CustomerId).HasColumnName("CustomerID");
        Property(t => t.RelatedId).HasColumnName("RelatedID");
        Property(t => t.PercentageShare).HasColumnName("PercentageShare");

        HasRequired(t => t.Expense)
            .WithMany(t => t.ExpenseRelationships)
            .HasForeignKey(t => t.RelatedId);
    }
}

Как только я создал запись "Карта", как показано выше, при запросе метода GetEmployments(), теперь я получаю следующее исключение:

"Тип объекта" ExpenseRelationship "и" EmploymentRelationship "не могут делиться таблицей 'tblCustomerRelationship', потому что они не находятся в иерархии одного и того же типа или не имеют действительного одного-одного внешнего ключа отношения с соответствующими первичными ключами между ними.",

Что мне не хватает?

UPDATE

Согласно комментариям jjj, я обновил свои сопоставления и создал базовый класс CustomerRelationship.cs.

public class Employment : EntityBase
{      
    public string EmployerName { get; set; }

    public string EmployerContactFirstName { get; set; }

    public string EmployerContactSurname { get; set; }

    public virtual ICollection<EmploymentRelationship> EmploymentRelationships { get; set; }
}

public class Expense : EntityBase
{
    public string Description { get; set; }

    public virtual ICollection<ExpenseRelationship> ExpenseRelationships { get; set; }
}

public abstract class CustomerRelationship : EntityBase
{
    public int CustomerId { get; set; }

    public int? CustomerRelationshipTypeId { get; set; }

    public int RelatedId { get; set; }
}

public class EmploymentRelationship : CustomerRelationship
{
    public virtual Employment Employment { get; set; }
}

public class ExpenseRelationship: CustomerRelationship
{
    public virtual Expense Expense{ get; set; }
}

public class CustomerRelationshipMap : BaseMap<CustomerRelationship>
{
    public CustomerRelationshipMap()
    {
        ToTable("CustomerRelationship");

        Map<EmploymentRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(55));
        Map<ExpenseRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(60));

        Property(t => t.Id).HasColumnName("CustomerRelationshipID");            
        Property(t => t.CustomerId).HasColumnName("CustomerID");
        Property(t => t.RelatedId).HasColumnName("RelatedID");            
    }

public class EmploymentRelationshipMap : BaseMap<EmploymentRelationship>
{
    public EmploymentRelationshipMap()
    {
        HasRequired(t => t.Employment)
            .WithMany(t => t.EmploymentRelationships)
            .HasForeignKey(t => t.RelatedId);
    }
}

public class ExpenseRelationshipMap : BaseMap<ExpenseRelationship>
{
    public ExpenseRelationshipMap()
    {
        HasRequired(t => t.Expense)
            .WithMany(t => t.ExpenseRelationships)
            .HasForeignKey(t => t.RelatedId);
    }
}

public class CustomerContext : BaseContext
{
    public CustomerContext()
        : base(context_connectionstring_main)
    {
    }

    public virtual DbSet<Customer> Customers { get; set; }
    public virtual DbSet<Employment> Employments { get; set; }

    public virtual DbSet<CustomerRelationship> CustomerRelationships { get; set; }
    public virtual DbSet<EmploymentRelationship> EmploymentRelationships { get; set; }
    public virtual DbSet<ExpenseRelationship> ExpenseRelationships { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new CustomerMap());
        modelBuilder.Configurations.Add(new EmploymentMap());

        modelBuilder.Configurations.Add(new CustomerRelationshipMap());
        modelBuilder.Configurations.Add(new EmploymentRelationshipMap());
        modelBuilder.Configurations.Add(new ExpenseRelationshipMap());
    }
}

Когда я запрашиваю контекст клиента следующим образом:

var relationships = context.CustomerRelationships.Where(l => l.CustomerId == customerId).ToList();

Я получаю следующее исключение:

"Компонент внешнего ключа 'RelatedId' не является объявленным свойством в тип" EmploymentRelationship ". Убедитесь, что он не был явно исключено из модели и что оно является допустимым примитивным свойством.",

Ответы

Ответ 1

Вам нужна конфигурация базового класса для всех общих свойств (включая первичный ключ).

public class CustomerRelationshipMap : EntityTypeConfiguration<CustomerRelationship>
{
    public CustomerRelationshipMap()
    {
        ToTable("tblCustomerRelationship");

        Map<EmploymentRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(55));
        Map<ExpenseRelationship>(m => m.Requires("CustomerRelationshipTypeID").HasValue(60));

        HasKey(t => t.Id);
        Property(t => t.Id).HasColumnName("CustomerRelationshipID");
        Property(t => t.CustomerId).HasColumnName("CustomerID");
        Property(t => t.RelatedId).HasColumnName("RelatedID");
    }
}

Затем вы должны иметь конфигурацию, зависящую от производного класса, в других классах конфигурации (хотя это не то, что я пробовал раньше).

Edit

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

  • Разделите внешние ключи для связи между EmploymentRelationship - Employment и ExpenseRelationship - Expense.
  • Предоставление Employment и Expense общего базового класса, хотя это может превзойти цель того, что вы пытаетесь сделать.
  • Отделить 1: 0..1 отношения между CustomerRelationship и Employment/Expense (и избавиться от EmploymentRelationship и ExpenseRelationship)
  • Наследование TPT, где Employment и Expense наследуются от CustomerRelationship (и избавляются от EmploymentRelationship и ExpenseRelationship)

Источники