Наследование и составные внешние ключи - одна часть ключа в базовом классе, другая часть в производном классе
у меня возникли проблемы с созданием сопоставления кода Entity Framework для следующей схемы базы данных (в SQL Server):
каждая таблица содержит TenantId
который является частью всех (составных) первичных и внешних ключей (Multi-Tenancy).
A Company
или Customer
или Supplier
и я пытаюсь смоделировать это с помощью сопоставления наследования Table-Per-Type (TPT):
public abstract class Company
{
public int TenantId { get; set; }
public int CompanyId { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
}
public class Customer : Company
{
public string CustomerName { get; set; }
public int SalesPersonId { get; set; }
public Person SalesPerson { get; set; }
}
public class Supplier : Company
{
public string SupplierName { get; set; }
}
отображение с беглым API:
modelBuilder.Entity<Company>()
.HasKey(c => new { c.TenantId, c.CompanyId });
modelBuilder.Entity<Customer>()
.ToTable("Customers");
modelBuilder.Entity<Supplier>()
.ToTable("Suppliers");
базовая таблица Companies
имеет отношение "один ко многим" к Address
(у каждой компании есть адрес, независимо от того, клиент или поставщик), и я могу создать отображение для этой ассоциации:
modelBuilder.Entity<Company>()
.HasRequired(c => c.Address)
.WithMany()
.HasForeignKey(c => new { c.TenantId, c.AddressId });
внешний ключ состоит из одной части первичного ключа -TenantId
- и отдельный столбец -AddressId
. Эта работа.
как вы можете видеть в схеме базы данных, с точки зрения базы данных, отношения между Customer
и Person
в основном такой же вид отношений один ко многим, как между Company
и Address
- внешний ключ снова состоит из TenantId
(часть первичного ключа) и столбец SalesPersonId
. (Только у клиента есть продавец, а не Supplier
, поэтому на этот раз отношение находится в производном классе, а не в базовом классе.)
я пытаюсь создать сопоставление для этого отношения с Fluent API так же, как и раньше:
modelBuilder.Entity<Customer>()
.HasRequired(c => c.SalesPerson)
.WithMany()
.HasForeignKey(c => new { c.TenantId, c.SalesPersonId });
но когда EF пытается скомпилировать модель InvalidOperationException
бросается:
компонент внешнего ключа "TenantId" не является объявленным свойством на введите "клиент". Убедитесь, что он не был исключен из модель и что она является допустимым свойством примитива.
по-видимому, я не могу составить внешний ключ из свойства в базовом классе и из другого свойства в производном классе (хотя в схеме базы данных внешний ключ состоит из колонки и в таблице производного типа Customer
).
я попробовал две модификации, чтобы заставить его работать, возможно:
-
изменена связь внешнего ключа между
Customer
иPerson
к Независимой ассоциации, т. е. удалена собственностьSalesPersonId
, а затем попробовал отображение:modelBuilder.Entity<Customer>() .HasRequired(c => c.SalesPerson) .WithMany() .Map(m => m.MapKey("TenantId", "SalesPersonId"));
это не помогает (я действительно не надеялся, что это будет), и исключение:
схема указана недействительный. ... Каждое имя свойства в типе должно быть уникальным. Имя свойства "TenantId" уже определено.
изменено отображение TPT на TPH, т. е. удалены два
ToTable
звонки. Но это вызывает то же исключение.
я вижу два способа решения:
-
вводим
SalesPersonTenantId
наCustomer
класс:public class Customer : Company { public string CustomerName { get; set; } public int SalesPersonTenantId { get; set; } public int SalesPersonId { get; set; } public Person SalesPerson { get; set; } }
и сопоставление:
modelBuilder.Entity<Customer>() .HasRequired(c => c.SalesPerson) .WithMany() .HasForeignKey(c => new { c.SalesPersonTenantId, c.SalesPersonId });
я проверил это, и это завод. Но у меня будет новая колонка
SalesPersonTenantId
наCustomers
таблица в дополнение кTenantId
. Этот столбец является излишним, поскольку оба столбца всегда должны иметь одинаковое значение с точки зрения бизнеса. отказаться от наследования отображения и создать один к одному сопоставлений между
Company
иCustomer
и междуCompany
иSupplier
.Company
должен стать конкретным типом, а не абстрактным, и у меня будет два свойства навигации вCompany
. Но эта модель не выразило бы правильно, что компания или клиент или поставщик и не может быть и тем и другим одновременно. Я не проверял, но думаю, что это сработает.
я вставляю полный пример, который я тестировал (консольное приложение, Ссылка на сборку EF 4.3.1, загруженную через NuGet), здесь, Если кто-то любит экспериментировать с ним:
using System;
using System.Data.Entity;
namespace EFTPTCompositeKeys
{
public abstract class Company
{
public int TenantId { get; set; }
public int CompanyId { get; set; }
public int AddressId { get; set; }
public Address Address { get; set; }
}
public class Customer : Company
{
public string CustomerName { get; set; }
public int SalesPersonId { get; set; }
public Person SalesPerson { get; set; }
}
public class Supplier : Company
{
public string SupplierName { get; set; }
}
public class Address
{
public int TenantId { get; set; }
public int AddressId { get; set; }
public string City { get; set; }
}
public class Person
{
public int TenantId { get; set; }
public int PersonId { get; set; }
public string Name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasKey(c => new { c.TenantId, c.CompanyId });
modelBuilder.Entity<Company>()
.HasRequired(c => c.Address)
.WithMany()
.HasForeignKey(c => new { c.TenantId, c.AddressId });
modelBuilder.Entity<Customer>()
.ToTable("Customers");
// the following mapping doesn't work and causes an exception
modelBuilder.Entity<Customer>()
.HasRequired(c => c.SalesPerson)
.WithMany()
.HasForeignKey(c => new { c.TenantId, c.SalesPersonId });
modelBuilder.Entity<Supplier>()
.ToTable("Suppliers");
modelBuilder.Entity<Address>()
.HasKey(a => new { a.TenantId, a.AddressId });
modelBuilder.Entity<Person>()
.HasKey(p => new { p.TenantId, p.PersonId });
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
using (var ctx = new MyContext())
{
try
{
ctx.Database.Initialize(true);
}
catch (Exception e)
{
throw;
}
}
}
}
}
вопрос: есть ли способ, чтобы сопоставить схему базы данных, чтобы модель класса с Entity Framework?
3 ответов
Ну, я не могу ничего комментировать, поэтому я добавляю это в качестве ответа.
Я создал проблему на CodePlex для этой проблемы, поэтому, надеюсь, они скоро ее рассмотрят. Оставайтесь с нами!
http://entityframework.codeplex.com/workitem/865
результатом проблемы в CodePlex (которая была закрыта в то же время) является то, что сценарий в вопросе не поддерживается, и в настоящее время нет планов по его поддержке в ближайшем будущем будущее.
цитата из команды Entity Framework в CodePlex:
Это часть более фундаментального ограничения, где EF не поддерживает наличие свойства, определенного в базовом типе, а затем его использование в качестве внешний ключ в производном типе. К сожалению, это ограничение, которое будет очень трудно удалить из нашей базы кода. Учитывая, что мы не видел много запросов на это, это не то, что мы планируем адрес на этом этапе, так что мы закрываем этот вопрос.
не решение, а обходной путь ( * ): хороший выбор-использовать одиночные столбцы Id (as), обычно автоматически увеличиваемые, и обеспечивать целостность базы данных с помощью внешних ключей, уникальных индексов и т. д. Более сложная целостность данных может быть достигнута с помощью триггеров, поэтому, возможно, вы могли бы двигаться в этом направлении, но вы можете оставить это на уровне бизнес-логики приложения, если приложение действительно не ориентировано на данные. Но поскольку вы используете Entity Framework, вероятно, можно с уверенностью предположить, что это не так ваш случай...?
(*) Как предложено ivowiblo
Я думаю, что проще и уменьшает сложность иметь таблицу --> TableId (PK) --> другие столбцы, включая FKs.
Итак, в вашем примере - добавление столбца CustomerId в таблицу Customers решит вашу проблему.