我正在使用Entity Framework 7和Code-First工作,我的模型涉及三级父子关系:
- “公司”拥有“企业” - “企业”隶属于“公司”,并拥有“工厂” - “工厂”隶属于“企业”
由于这三个实体有许多共同点,它们都继承自抽象的“BaseOrganization”实体。
当我尝试列出所有工厂,包括它们的母公司,然后包括它们的母公司所属的集团时,我有以下两种不同的情况:
您需要包含以下 NuGet 包:
- “公司”拥有“企业” - “企业”隶属于“公司”,并拥有“工厂” - “工厂”隶属于“企业”
由于这三个实体有许多共同点,它们都继承自抽象的“BaseOrganization”实体。
当我尝试列出所有工厂,包括它们的母公司,然后包括它们的母公司所属的集团时,我有以下两种不同的情况:
- 不包括
BaseOrganization
到上下文中,使用 Code-First 创建了三个表(对应于具体类型的表继承或 TPC 模式)。Include()
和ThenInclude()
正常工作,我可以按预期列出工厂并遍历关系。 - 包括
BaseOrganization
到上下文中,使用 Code-First 创建了带有鉴别器字段的一个表(对应于层次结构表继承或 TPH 模式)。Include()
和ThenInclude()
抛出一个Sequence contains more than one matching element
异常。
此问题(没有继承和抽象基类模式)已经在 EF7 Github 存储库中得到解决,并已被清除(请参见 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"
更新
正如我在自己的评论中所说,我的第一个解决方法是避免在DbContext中包含基本类型,这样代码优先生成TPC模式的架构(仅当使用TPH策略时才会出现错误)。
问题在于上面的示例比我的实际实现要简单,其中涉及到在基本类型级别定义的多对多关系。
由于EF7尚未支持多对多关系,因此我们必须定义一个链接实体,它将两个一对多关系映射到自己身上。
即使在基本类型级别定义和使用了该映射实体,代码优先仍然选择TPH策略,然后错误仍然会发生。
换句话说,我被卡住了,或者我将不得不将某些逻辑复制三次,这几乎就像故意折断自己的腿!
Include().ThenInclude()
来加载关系来解决问题。但我不喜欢这种情况下,Code-First会生成3个不同的表,因为它不反映SQL模式中的继承,更不用说共享字段将在每个表中重复出现的事实了。 - kall2sollies