我在为以下示例数据库模式(在SQL Server中)创建实体框架代码优先映射时遇到问题:
每个表都包含TenantId
,它是所有(复合)主键和外键(多租户)的一部分。
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; }
}
使用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 });
外键由主键的一部分 - TenantId
- 和单独的列 - AddressId
组成。这有效。
正如您在数据库架构中看到的那样,从数据库角度来看,Customer
和Person
之间的关系与Company
和{之间的一对多关系基本相同{1}} - 外键再次由Address
(主键的一部分)和列TenantId
组成。 (只有客户有销售人员,而不是SalesPersonId
,因此这次关系在派生类中,而不是在基类中。)
我尝试使用与之前相同的方式为Fluent API创建此关系的映射:
Supplier
但是当EF尝试编译模型时,会抛出modelBuilder.Entity<Customer>()
.HasRequired(c => c.SalesPerson)
.WithMany()
.HasForeignKey(c => new { c.TenantId, c.SalesPersonId });
:
外键组件'TenantId'不是声明的属性 输入“客户”。确认未明确排除 模型,它是一个有效的原始属性。
显然我不能从基类中的属性和派生类中的另一个属性组成外键(尽管在数据库模式中,外键由派生类型中的列 组成)表InvalidOperationException
)。
我尝试了两次修改以使其正常工作:
将Customer
和Customer
之间的外键关联更改为独立关联,即删除了属性Person
,然后尝试了映射:
SalesPersonId
它没有帮助(我真的不希望,它会)并且例外是:
指定的架构无效。 ...类型中的每个属性名称都必须 独一无二。已定义属性名称“TenantId”。
将TPT更改为TPH映射,即删除了两个modelBuilder.Entity<Customer>()
.HasRequired(c => c.SalesPerson)
.WithMany()
.Map(m => m.MapKey("TenantId", "SalesPersonId"));
个调用。但它引发了同样的例外。
我看到两个解决方法:
在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
中有两个导航属性。但是这种模式不能正确表达公司 客户或供应商,而不能同时存在。我没有测试它,但我相信它会起作用。
如果有人喜欢试用它,我会在这里粘贴我测试过的完整示例(控制台应用程序,对EF 4.3.1程序集的引用,通过NuGet下载):
Company
问题:有没有办法将上面的数据库模式映射到具有Entity Framework的类模型?
答案 0 :(得分:7)
好吧,我似乎无法对任何事情发表评论,所以我将其作为答案添加。
我为CodePlex创建了一个针对此问题的问题,希望他们能尽快调查。请继续关注!
http://entityframework.codeplex.com/workitem/865
CodePlex问题的结果(在此期间已经关闭)是问题中的情景不受支持,目前没有计划在不久的将来支持它。
来自CodePlex的实体框架团队的报价:
这是EF不支持的更基本限制的一部分 具有在基本类型中定义的属性,然后将其用作 派生类型中的外键。不幸的是,这是一个限制 将很难从我们的代码库中删除。鉴于我们没有 看到了很多要求,这不是我们计划的 现阶段解决这个问题。
答案 1 :(得分:0)
不是解决方案,而是解决方法(*):一个不错的选择是使用单个Id列(as),通常是自动递增的,并使用外键,唯一索引等提供数据库完整性。更复杂的数据完整性可以使用触发器实现,所以也许你可能会以这种方式前进,但你可能会将其留给应用程序业务逻辑级别,除非应用程序确实是以数据为中心的。但是,既然您正在使用实体框架,那么可以安全地假设这不是您的情况......?
按照ivowiblo 的建议(*)
答案 2 :(得分:0)
我认为它更简单并且降低了复杂性以使表格 - &gt; TableId(PK) - &gt;其他专栏包括FK。
因此,在您的示例中 - 将CustomerId列添加到Customers表将解决您的问题。