EF 6 - Кодирование первого недопустимого отношения "один к одному" внешнего ключа
ПРЕДПОСЫЛКИ КОНСТРУКЦИИ:
Я пытаюсь создать сопоставления EF6 с кодовым кодом для следующей структуры базы данных:
Конструкция базы данных такова: Вместо того, чтобы иметь "CustomerID" как внешний ключ для всех связанных лиц (занятость, расходы, доход и т.д.), у нас есть таблица CustomerRelationship, в которой будет идентификатор CustomerID, а затем столбец "Связанный идентификатор", который будет содержать ключ связанного объекта. Например, скажем, я добавляю запись занятости для CustomerID = 1, тогда произойдет следующее:
-
Создайте запись в CustomerRelationship, установив CustomerID = 1 RelatedID = {новый автогенерированный идентификатор занятости, скажем 5} CustomerRelationshipTypeID = 55 (идентификатор в таблице поиска, в котором указано, что эта запись относится к типу занятости)
-
Создать запись в таблице занятости (EmploymentID = 5)
Вышеупомянутая структура будет работать для всех объектов, привязанных к клиенту.
У меня есть сопоставления отношений, работающие для Занятости, вот мои классы:
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
)
Источники