Sla*_*uma 17 .net inheritance entity-framework table-per-type ef-code-first
我在为以下示例数据库模式(在SQL Server中)创建实体框架代码优先映射时遇到问题:

每个表都包含一个TenantId,它是所有(复合)主键和外键(多租户)的一部分.
A Company是a Customer或a 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; }
}
Run Code Online (Sandbox Code Playgroud)
使用Fluent API进行映射:
modelBuilder.Entity<Company>()
.HasKey(c => new { c.TenantId, c.CompanyId });
modelBuilder.Entity<Customer>()
.ToTable("Customers");
modelBuilder.Entity<Supplier>()
.ToTable("Suppliers");
Run Code Online (Sandbox Code Playgroud)
基表Companies与Address(一个公司有地址,无论是客户还是供应商)有一对多的关系,我可以为这种关联创建一个映射:
modelBuilder.Entity<Company>()
.HasRequired(c => c.Address)
.WithMany()
.HasForeignKey(c => new { c.TenantId, c.AddressId });
Run Code Online (Sandbox Code Playgroud)
外键由主键的一部分组成TenantId- 和 - 一个单独的列 - AddressId.这有效.
正如您在数据库模式中看到的那样,从数据库的角度来看,Customer和之间的关系Person基本上是同一种一对多的关系,Company而且Address- 外键是由TenantId(主键的一部分)和列组成的SalesPersonId.(只有客户有销售人员,而不是a Supplier,因此这次关系是在派生类中,而不是在基类中.)
我尝试使用与之前相同的方式为Fluent API创建此关系的映射:
modelBuilder.Entity<Customer>()
.HasRequired(c => c.SalesPerson)
.WithMany()
.HasForeignKey(c => new { c.TenantId, c.SalesPersonId });
Run Code Online (Sandbox Code Playgroud)
但是当EF尝试编译模型时,InvalidOperationException抛出了:
外键组件"TenantId"不是"Customer"类型的声明属性.验证它是否未从模型中明确排除,并且它是有效的原始属性.
显然,我不能在派生类中基类的属性,并从另一个属性构成一个外键(虽然在数据库架构中的外键由列都在派生类型的表Customer).
我尝试了两次修改以使其正常工作:
改变之间的外键关联Customer,并Person以独立相关,即移除属性SalesPersonId,然后试图映射:
modelBuilder.Entity<Customer>()
.HasRequired(c => c.SalesPerson)
.WithMany()
.Map(m => m.MapKey("TenantId", "SalesPersonId"));
Run Code Online (Sandbox Code Playgroud)
它没有帮助(我真的不希望,它会),例外是:
指定的架构无效....类型中的每个属性名称必须是唯一的.已定义属性名称"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; }
}
Run Code Online (Sandbox Code Playgroud)
和映射:
modelBuilder.Entity<Customer>()
.HasRequired(c => c.SalesPerson)
.WithMany()
.HasForeignKey(c => new { c.SalesPersonTenantId, c.SalesPersonId });
Run Code Online (Sandbox Code Playgroud)
我测试了它,它的工作原理.但除了我之外,我还会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;
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
问题:有没有办法将上面的数据库模式映射到具有Entity Framework的类模型?
好吧,我似乎无法对任何事情发表评论,所以我将其作为答案添加.
我在CodePlex上为这个问题创建了一个问题,希望他们能尽快调查.敬请关注!
http://entityframework.codeplex.com/workitem/865
CodePlex问题的结果(在此期间已经关闭)是问题中的情景不受支持,目前没有计划在不久的将来支持它.
来自CodePlex的实体框架团队的报价:
这是更基本的限制的一部分,其中EF不支持在基类型中定义属性,然后将其用作派生类型中的外键.不幸的是,这是一个很难从我们的代码库中删除的限制.鉴于我们没有看到很多请求,这不是我们计划在这个阶段解决的问题所以我们正在关闭这个问题.