包括()ThenInclude()抛出"序列包含多个匹配的元素"例外表中的每个层级战略 [英] Include() ThenInclude() throws "Sequence contains more than one matching element" exception in Table Per Hierarchy strategy

查看:1771
本文介绍了包括()ThenInclude()抛出"序列包含多个匹配的元素"例外表中的每个层级战略的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我与实体框架7和代码第一份工作,我有涉及3个级别的父子关系的典范:




  • 企业公司

  • 公司属于公司工厂

  • 工厂属于公司



由于这3个实体共享许多共同点,它们都从一个抽象的 BaseOrganization 实体。



<继承p >当我尝试列出所有工厂,包括其母公司,然后包括他们的母亲的公司,我有这两种不同的情况:




  • 不包括 BaseOrganization 成的背景下,代码首先创建三表(对应于表格-per混凝土-Type或TPC 模式)。 包括() ThenInclude()做工精细,并如预期我可以列出工厂和横向关系。

  • BaseOrganization 成的背景下,代码首先创建一个表与鉴别域(对应于表格,每层次或TPH 模式)。 包括() ThenInclude()抛出一个序列包含多个匹配的元素例外。



这问题(没有继承和抽象基类模式)已经不客气的EF7 GitHub库,并已被清理(见 https://github.com/aspnet/EntityFramework/issues/1460)。



所以,我目前不知道是不是我的做法有什么不对,或者如果这显然是与EF7 RC1的问题吗?注意,我真的喜欢保持继承,以便我的SQL模型变得更具可读性。



以下是完整再现代码:

 使用系统; 
使用System.Collections.Generic;
使用System.Linq的;
使用Microsoft.Data.Entity;

命名空间MultiLevelTest
{
//所有地方共享的名称和ID
公共抽象类BaseOrganization
{
公众诠释标识{搞定;组; }
公共字符串名称{;组; }
}

//的公司(如:空客集团)
公共类公司:BaseOrganization
{
公共虚拟的ICollection<公司>公司{搞定;组; } =新的List<公司+ GT;();
}

//公司(如:空中客车,空中客车直升机公司,阿丽亚娜航天公司)
公共类公司:BaseOrganization
{
公共虚拟公司公司{得到;组; }
公共虚拟的ICollection<工厂及GT;工厂{搞定;组; } =新的List<工厂及GT;();
}

//公司(空客图卢兹,空客美国......)
公共类厂的工厂:BaseOrganization
{
公共虚拟公司简介公司{搞定;组; }
}

//设置的DbContext
公共类MyContext:的DbContext
{
//如果这一行被注释掉,那么代码首先创建3个表而不是一个,和一切工作正常。
公共DbSet< BaseOrganization> BaseOrganizationCollection {搞定;组; }
公共DbSet<公司+ GT;企业{搞定;组; }
公共DbSet<公司+ GT;公司{搞定;组; }
公共DbSet<工厂及GT;工厂{搞定;组; }

保护覆盖无效OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@服务器=(的LocalDB)\mssqllocaldb;数据库= MultiLevelTest; Trusted_Connection = TRUE; MultipleActiveResultSets =真正的);
}

保护覆盖无效OnModelCreating(模型构建器模型构建器)
{
base.OnModelCreating(模型构建器);

modelBuilder.Entity&所述;株式会社方式>()的hasMany(C => c.Companies).WithOne(C => c.Corporation);
modelBuilder.Entity&所述;公司方式>()的hasMany(C => c.Factories).WithOne(C => c.Company);
modelBuilder.Entity<工厂方式>()HasOne(F => f.Company);
}
}

公共类节目
{
公共静态无效的主要(字串[] args)
{
使用(VAR CTX =新MyContext())
{
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();

//添加公司与企业然后工厂(这工作正常)
如果CreateOrganizationGraph(CTX)(ctx.Corporations.Any()!);

//获取所有的工厂,而不包括任何东西(这仍是做工精细)
VAR simpleFactories = ctx.Factories.ToList();
的foreach(在simpleFactories变种F)Console.WriteLine(f.Name);

//获取所有工厂,包括他们的母亲的公司,那么他们的母亲法人
VAR fullFactories = ctx.Factories
.INCLUDE(F => f.Company)
.ThenInclude(C => c.Corporation)
.ToList();
的foreach(在fullFactories变种F)Console.WriteLine(${f.Company.Corporation.Name}> {f.Company.Name}> {} f.Name);
}
}

公共静态无效CreateOrganizationGraph(MyContext CTX)
{
变种airbusCorp =新公司()
{
产品名称=空中客车集团,
公司=新的List<公司>()
{
新公司
{
NAME =空中客车,
介子工厂=新的List<工厂>()
{
新厂{NAME =空客图卢兹(FR)},
新厂{NAME =空客Hambourg(DE)}
}
},
新公司
{
NAME =空中客车直升机,
工厂=新的List<工厂>()
{
新厂{NAME =欧直马里尼亚讷(FR)},
新厂{NAME =欧洲直升机德国(DE)}
}
}
}
};

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

}
}
}

您要包括以下的NuGet包:

 EntityFramework.Commands:7.0.0-RC1决赛,
EntityFramework.Core:7.0.0-RC1决赛,
EntityFramework.MicrosoftSqlServer:7.0.0-RC1-最后的



更新



正如我在自己的评论说,我的第一个解决方法是避免包括基本类型在的DbContext,使代码首先生成模式与TPC模式(臭虫TPH策略只抛出时)。



问题是,上面的例子是我的简单实际执行中,其中涉及到许多一对多的基本类型级别定义的关系。



由于EF7不(没?)支持许多一对多的关系,我们有以限定它映射两个在它自己的一对多关系的链接实体



这被定义并在基部型水平使用变换实体,代码优先还是选择采用对于TPH策略,然后将错误仍然抛出。



在换句话说,我坚持,否则我将不得不重复一些逻辑的三倍,这几乎是像打破我自己的腿故意的!


解决方案

我觉得你不应该尝试在你的情况下,使用基类。



团体,公司,工厂代表不同的对象,从我看到这里,您试图重构代码不是抽象的对象:



如果您创建存储作者和图书,作者和书籍将有一个名称和ID,但它是有意义的有一个基类数据库?



对于你一定会节省几行代码,但它会使你的代码的可读性



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



例如,你可以有一个基类管理员工类,它从Person类,因为员工和经理人继承。



有关我,你只需要删除您的基类,它应该按预期工作:

 公共类公司
{
公众诠释标识{搞定;组; }
公共字符串名称{;组; }
公开名单<公司+ GT;公司{搞定;组; } =新的List<公司+ GT;();
}

公共类公司
{
公众诠释标识{搞定;组; }
公共字符串名称{;组; }
公社总公司{搞定;组; }
公开名单<工厂及GT;工厂{搞定;组; } =新的List<工厂及GT;();
}

公共类厂
{
公众诠释标识{搞定;组; }
公共字符串名称{;组; }
上市公司的公司{搞定;组; }
}

公共类MyContext:的DbContext
{
公共DbSet<公司+ GT;企业{搞定;组; }
公共DbSet<公司+ GT;公司{搞定;组; }
公共DbSet<工厂及GT;工厂{搞定;组; }

...

保护覆盖无效OnModelCreating(模型构建器模型构建器)
{
modelBuilder.Entity<公司方式>()的hasMany(C = > c.Companies).WithOne(C => c.Corporation);
modelBuilder.Entity&所述;公司方式>()的hasMany(C => c.Factories).WithOne(C => c.Company);
modelBuilder.Entity<工厂方式>()HasOne(F => f.Company);
}
}


I am working with Entity Framework 7 and code-first, and I have a model which involves parent-child relations on 3 levels :

  • Corporations have companies
  • Companies belong to a corporation and have factories
  • Factories belong to a company

Since these 3 entities share a lot in common, they all inherit from an abstract BaseOrganization entity.

When I try to list all the factories, including their mother companies, and then including their mother corporations, I have these two different scenarios :

  • Without including BaseOrganization into the context, code-first creates three tables (which corresponds to the Table-Per-Concrete-Type or TPC pattern). Include() and ThenInclude() work fine, and I can list factories and traverse relations as expected.
  • Including BaseOrganization into the context, code-first creates one table with a discriminator field (which corresponds to the Table-Per-Hierarchy or TPH pattern). Include() and ThenInclude() throw a Sequence contains more than one matching element exception.

This issue (without the inheritance and abstract base class pattern) was already adressed in EF7 Github repo, and had been cleared (see https://github.com/aspnet/EntityFramework/issues/1460).

So I currently don't know if my approach has something wrong, or if this is clearly an issue with EF7 RC1 ? Note that I'd really prefer keeping inheritance so that my SQL model gets far more readable.

Below is the full reproduction code :

    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();

            }
        }
    }

You will want to include the following NuGet packages :

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

UPDATE

As said in my own comments, my first workaround would be to avoid including the base type in the DbContext, so that code-first generates schema with TPC pattern (the bug throws only when in TPH strategy).

Problem is that the example above is simpler as my actual implementation, which involves many-to-many relationships defined at base type level.

Since EF7 does not (yet?) support many-to-many relationships, we have to define a link entity which maps two one-to-many relationships on its own.

That mapping entity being defined and used at base type level, code-first still opts for TPH strategy, and then the bug still throws.

In other words, I'm stuck, or I will have to duplicate some logic three times, which is almost like breaking my own leg on purpose !

解决方案

I think you should not try to use a base class in your case.

Organizations, companies, factories represents different objects and from what I see here, you are trying to refactor code not abstracting object:

If you create a database that store authors and books, both authors and books will have a name and an id but does it make sense to have a base class ?

For sure you are going to save few lines of code but it will make you code less readable.

I think you should use a base class when there is a real inheritance:

For example, you can have a base class Person and a Manager and Employee class that inherits from the Person class because both employees and managers are persons.

For me you just have to remove your base class and It should work as expected :

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);
    }
}

这篇关于包括()ThenInclude()抛出&QUOT;序列包含多个匹配的元素&QUOT;例外表中的每个层级战略的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆