【问题标题】:Include() ThenInclude() throws "Sequence contains more than one matching element" exception in Table Per Hierarchy strategyInclude() ThenInclude() 在 Table Per Hierarchy 策略中抛出“序列包含多个匹配元素”异常
【发布时间】:2016-04-11 16:29:49
【问题描述】:

我正在使用 Entity Framework 7 和代码优先,并且我有一个涉及 3 个级别的父子关系的模型:

  • Corporationscompanies
  • Companies 属于 corporation 并拥有 factories
  • Factories 属于company

由于这 3 个实体有很多共同点,它们都继承自抽象的 BaseOrganization 实体。

当我尝试列出所有工厂,包括他们的母公司,然后包括他们的母公司时,我有这两种不同的情况:

  • 在上下文中不包含BaseOrganization,代码先创建三个表(对应于Table-Per-Concrete-Type 或TPC图案)。 Include()ThenInclude() 工作正常,我可以按预期列出工厂和遍历关系。
  • 包含 BaseOrganization 到上下文中,代码先创建一个表,其中包含一个鉴别器字段(对应于Table-Per-Hierarchy 或TPH 模式)。 Include()ThenInclude() 抛出 Sequence contains more than one matching element 异常。

这个问题(没有继承和抽象基类模式)已经在 EF7 Github repo 中得到解决,并且已经被清除(参见https://github.com/aspnet/EntityFramework/issues/1460)。

所以我目前不知道我的方法是否有问题,或者这显然是 EF7 RC1 的问题?请注意,我真的更喜欢保留继承,以便我的 SQL 模型变得更具可读性。

以下是完整的复制代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Data.Entity;

    namespace MultiLevelTest
    {
        // All places share name and Id
        public abstract class BaseOrganization
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }

        // a corporation (eg : Airbus Group)
        public class Corporation : BaseOrganization
        {
            public virtual ICollection<Company> Companies { get; set; } = new List<Company>();
        }

        // a company (eg : Airbus, Airbus Helicopters, Arianespace)
        public class Company : BaseOrganization
        {
            public virtual Corporation Corporation { get; set; }
            public virtual ICollection<Factory> Factories { get; set; } = new List<Factory>();
        }

        // a factory of a company (Airbus Toulouse, Airbus US...)
        public class Factory : BaseOrganization
        {
            public virtual Company Company { get; set; }
        }

        // setup DbContext
        public class MyContext : DbContext
        {
            // if this line is commented, then code first creates 3 tables instead of one, and everything works fine.
            public DbSet<BaseOrganization> BaseOrganizationCollection { get; set; }
            public DbSet<Corporation> Corporations { get; set; }
            public DbSet<Company> Companies { get; set; }
            public DbSet<Factory> Factories { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(
                    @"Server=(localdb)\mssqllocaldb;Database=MultiLevelTest;Trusted_Connection=True;MultipleActiveResultSets=true");
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);

                modelBuilder.Entity<Corporation>().HasMany(c => c.Companies).WithOne(c => c.Corporation);
                modelBuilder.Entity<Company>().HasMany(c => c.Factories).WithOne(c => c.Company);
                modelBuilder.Entity<Factory>().HasOne(f => f.Company);
            }
        }

        public class Program
        {
            public static void Main(string[] args)
            {
                using (var ctx = new MyContext())
                {
                    ctx.Database.EnsureDeleted();
                    ctx.Database.EnsureCreated();

                    // Add a corporation with companies then factories (this works fine)
                    if (!ctx.Corporations.Any()) CreateOrganizationGraph(ctx);

                    // Get all the factories without including anything (this is still working fine)
                    var simpleFactories = ctx.Factories.ToList();
                    foreach(var f in simpleFactories) Console.WriteLine(f.Name);

                    // Get all the factories including their mother company, then their mother corporation
                    var fullFactories = ctx.Factories
                        .Include(f => f.Company)
                        .ThenInclude(c => c.Corporation)
                        .ToList();
                    foreach (var f in fullFactories) Console.WriteLine($"{f.Company.Corporation.Name} > {f.Company.Name} > {f.Name}");
                }
            }

            public static void CreateOrganizationGraph(MyContext ctx)
            {
                var airbusCorp = new Corporation()
                {
                    Name = "Airbus Group",
                    Companies = new List<Company>()
                            {
                                new Company
                                {
                                    Name = "Airbus",
                                    Factories = new List<Factory>()
                                    {
                                        new Factory {Name = "Airbus Toulouse (FR)"},
                                        new Factory {Name = "Airbus Hambourg (DE)"}
                                    }
                                },
                                new Company
                                {
                                    Name = "Airbus Helicopters",
                                    Factories = new List<Factory>()
                                    {
                                        new Factory {Name = "Eurocopter Marignane (FR)"},
                                        new Factory {Name = "Eurocopter Deutschland (DE)"}
                                    }
                                }
                            }
                };

                ctx.Corporations.Add(airbusCorp);
                ctx.SaveChanges();

            }
        }
    }

您需要包含以下 NuGet 包:

"EntityFramework.Commands": "7.0.0-rc1-final",
"EntityFramework.Core": "7.0.0-rc1-final",
"EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final"

更新

正如我在自己的 cmets 中所说,我的第一个解决方法是避免在 DbContext 中包含基本类型,以便代码优先生成具有 TPC 模式的架构(该错误仅在 TPH 策略中引发)。

问题是上面的例子比我的实际实现更简单,它涉及在基类型级别定义的多对多关系。

由于 EF7 不(还?)支持多对多关系,我们必须定义一个链接实体,它自己映射两个一对多关系。

在基本类型级别定义和使用的映射实体,代码优先仍然选择 TPH 策略,然后错误仍然抛出。

也就是说,我被卡住了,否则我将不得不将某些逻辑重复三遍,这几乎就像故意打断自己的腿一样!

【问题讨论】:

  • 我认为我的解决方法就是不在 DbContext 中包含抽象基类型。这将通过允许我使用Include().ThenInclude() 加载关系来解决问题。但我不喜欢在这种情况下,代码优先将生成 3 个不同的表,因为它不反映 SQL 模式中的继承,更不用说共享字段将在每个表中重复的事实。
  • 我还在 GitHub 存储库上发布了基本相同的内容,因为我非常确信这是 EF7 的问题。 github.com/aspnet/EntityFramework/issues/5033

标签: c# .net inheritance entity-framework-core


【解决方案1】:

我认为你不应该尝试在你的情况下使用基类。

组织、公司、工厂代表不同的对象,从我在这里看到的情况来看,您正在尝试重构代码而不是抽象对象:

如果您创建一个存储作者和书籍的数据库,那么作者和书籍都会有一个名称和一个 ID,但是拥有一个基类是否有意义?

您肯定会节省几行代码,但这会使您的代码可读性降低。

我认为当有真正的继承时你应该使用基类:

例如,您可以有一个基类 Person 和一个继承自 Person 类的 ManagerEmployee 类,因为员工和经理都是人。

对我来说,你只需要删除你的基类,它应该可以按预期工作:

public class Corporation
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Company> Companies { get; set; } = new List<Company>();
}

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Corporation Corporation { get; set; }
    public List<Factory> Factories { get; set; } = new List<Factory>();
}

public class Factory
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Company Company { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Corporation> Corporations { get; set; }
    public DbSet<Company> Companies { get; set; }
    public DbSet<Factory> Factories { get; set; }

    ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Corporation>().HasMany(c => c.Companies).WithOne(c => c.Corporation);
        modelBuilder.Entity<Company>().HasMany(c => c.Factories).WithOne(c => c.Company);
        modelBuilder.Entity<Factory>().HasOne(f => f.Company);
    }
}

【讨论】:

  • 感谢您的回复。为简洁起见,我的示例按原样提供。在我的实际实现中,我的类将共享更多——当然——只是一个 Id 和 Name(不过,我也有一个带有接口的基本类型,仅用于 Id 和 Name,它工作正常)。正如我的更新中所指出的,我的课程有一个人员列表,以多对多关系链接。由于 EF7 还不支持这些,我有一些逻辑来公开对 Persons 集合的直接访问,并且必须重复该逻辑,除非我在 Organization 基类中分解它。
  • 我还认为,从功能的角度来看,公司、公司和工厂从组织继承是有意义的,因为......它们是组织!可以共享很多字段,例如地址、人员、客户等...
  • 好的,谢谢你的解释。它从业务角度出发,但您的数据模型应该反映您的数据库。您可以拥有反映此逻辑的域对象
  • 无论如何,EF7 支持多对多关系(这并不完美......):ef.readthedocs.org/en/latest/modeling/…。这个解决方案可以接受吗?
  • 这就是我之前所说的:您可以像在 SQL 模式中所做的那样,通过枢轴实体对多对多 rel 进行建模。这就是我所做的。希望像这样的 EF6 的其余功能将很快在 EF7 中可用。不过,还没有阅读路线图!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多