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

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)

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

 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调用.但它抛出同样的例外.

我看到两个解决方法:

  • SalesPersonTenantIdCustomer课堂上介绍一下:

    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)

    我测试了它,它的工作原理.但除了我之外,我还会SalesPersonTenantIdCustomers表格中添加一个新列TenantId.此列是多余的,因为两个列始终必须具有来自业务角度的相同值.

  • 放弃继承映射之间建立一个一对一的映射CompanyCustomer之间CompanySupplier.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的类模型?

MDA*_*Dev 7

好吧,我似乎无法对任何事情发表评论,所以我将其作为答案添加.

我在CodePlex上为这个问题创建了一个问题,希望他们能尽快调查.敬请关注!

http://entityframework.codeplex.com/workitem/865


CodePlex问题的结果(在此期间已经关闭)是问题中的情景不受支持,目前没有计划在不久的将来支持它.

来自CodePlex的实体框架团队的报价:

这是更基本的限制的一部分,其中EF不支持在基类型中定义属性,然后将其用作派生类型中的外键.不幸的是,这是一个很难从我们的代码库中删除的限制.鉴于我们没有看到很多请求,这不是我们计划在这个阶段解决的问题所以我们正在关闭这个问题.