17

我在为以下示例数据库架构(在 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的一对多关系相同——外键再次由(主键的一部分)和列组成. (只有一个客户有一个销售人员,而不是一个,因此这次关系在派生类中,而不是在基类中。)CompanyAddressTenantIdSalesPersonIdSupplier

我尝试以与以前相同的方式为这种与 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在课堂上引入 a 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_ 此列是多余的,因为从业务角度来看,两列必须始终具有相同的值。CustomersTenantId

  • Company放弃继承映射,在and 和andCustomer之间创建一对一的映射。那么必须成为一个具体的类型,而不是抽象的,我将在. 但是这个模型不能正确地表达一个公司要么是客户要么是供应商,并且不能同时是两者。我没有测试它,但我相信它会起作用。CompanySupplierCompanyCompany

如果有人喜欢尝试它,我会将我测试过的完整示例(控制台应用程序,参考 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;
                }
            }
        }
    }
}

问题:有没有办法将上面的数据库模式映射到具有实体框架的类模型?

4

3 回答 3

7

好吧,我似乎无法评论任何事情,所以我将其添加为答案。

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

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


CodePlex 问题的结果(同时已关闭)是问题中的场景不受支持,目前没有计划在不久的将来支持它。

来自 CodePlex 实体框架团队的引述:

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

于 2013-02-14T16:59:36.977 回答
0

不是解决方案,而是解决方法(*):一个不错的选择是使用单个 Id 列(as),通常是自动递增的,并使用外键、唯一索引等提供数据库完整性。更复杂的数据完整性可以通过触发器,所以也许您可能会朝着那个方向前进,但您可能会将其留给应用程序业务逻辑级别,除非应用程序真的以数据为中心。但是由于您使用的是实体框架,因此可以安全地假设这不是您的情况......?

(*) 由 ivowiblo 建议

于 2012-06-18T14:34:13.870 回答
0

我认为拥有 Table --> TableId (PK) --> 其他列(包括 FK)更简单并降低了复杂性。

因此,在您的示例中 - 将 CustomerId 列添加到 Customers 表将解决您的问题。

于 2012-09-16T15:27:12.043 回答