我在为以下示例数据库架构(在 SQL Server 中)创建实体框架代码优先映射时遇到问题:
每个表都包含一个TenantId
作为所有(复合)主键和外键(多租户)的一部分。
ACompany
是 aCustomer
或 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; }
}
使用 Fluent 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 });
外键由主键的一部分 - the TenantId
- 和一个单独的列 - the组成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
在课堂上引入 aCustomer
: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
放弃继承映射,在and 和andCustomer
之间创建一对一的映射。那么必须成为一个具体的类型,而不是抽象的,我将在. 但是这个模型不能正确地表达一个公司要么是客户要么是供应商,并且不能同时是两者。我没有测试它,但我相信它会起作用。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;
}
}
}
}
}
问题:有没有办法将上面的数据库模式映射到具有实体框架的类模型?