继承和复合外键 - 基类中键的一部分,派生类中的另一部分

时间:2012-06-09 14:12:21

标签: .net entity-framework inheritance ef-code-first table-per-type

我在为以下示例数据库模式(在SQL Server中)创建实体框架代码优先映射时遇到问题:

Database schema with composite foreign keys

每个表都包含TenantId,它是所有(复合)主键和外键(多租户)的一部分。

CompanyCustomerSupplier,我尝试通过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");

基表CompaniesAddress具有一对多的关系(每家公司都有一个地址,无论是客户还是供应商),我可以为此关联创建映射:

 modelBuilder.Entity<Company>()
     .HasRequired(c => c.Address)
     .WithMany()
     .HasForeignKey(c => new { c.TenantId, c.AddressId });

外键由主键的一部分 - TenantId - 和单独的列 - AddressId组成。这有效。

正如您在数据库架构中看到的那样,从数据库角度来看,CustomerPerson之间的关系与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)。

我尝试了两次修改以使其正常工作:

  • CustomerCustomer之间的外键关联更改为独立关联,即删除了属性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。此列是多余的,因为从业务角度来看,这两列始终必须具有相同的值。

  • 放弃继承映射并在TenantIdCompany之间以及CustomerCompany之间创建一对一映射。 Supplier必须成为具体类型,而不是抽象,我在Company中有两个导航属性。但是这种模式不能正确表达公司 客户供应商,而不能同时存在。我没有测试它,但我相信它会起作用。

如果有人喜欢试用它,我会在这里粘贴我测试过的完整示例(控制台应用程序,对EF 4.3.1程序集的引用,通过NuGet下载):

Company

问题:有没有办法将上面的数据库模式映射到具有Entity Framework的类模型?

3 个答案:

答案 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表将解决您的问题。