首页 > 解决方案 > EF Core 循环往复 - 不同父母的多个实体集合

问题描述

一直在花费大量时间谷歌搜索并寻找解决方案,但没有更接近。

我有一系列对象:

目前它们都被定义为简单的类——我将在稍后创建每个类的细节:

public class ItemName
{
public Guid ItemNameId {get; set;}
public string Name {get; set;}
}

ItemName假设、风险、任务等在哪里...

这些项目可以由单个顾问、客户、计划或项目“拥有”

这些定义为:

public class ParentType
{
public Guid ParentTypeId {get; set;}
public virtual ICollection<Assumption> Assumptions {get; set;}
public virtual ICollection<dependency> Dependencies {get; set;}
public virtual ICollection<Issue> Issues {get; set;}
public virtual ICollection<Risk> Risks {get; set;}
public virtual ICollection<Task> Tasks {get; set;}
...remaining c10 Icollections
}

ParentType咨询,客户,计划,项目在哪里。

我已经在 DbContext 中定义了所有这些表

public DbSet<Assumption> Assumptions { get; set; }
public DbSet<Client> Clients { get; set; }
public DbSet<Consultancy> Consultancies { get; set; }
public DbSet<Dependency> Dependencies { get; set; }
public DbSet<Issue> Issues { get; set; }
public DbSet<Programme> Programmes { get; set; }
public DbSet<Project> Projects { get; set; }
public DbSet<Risk> Risks { get; set; }
public DbSet<Task> Tasks { get; set; }

如何在 DbContextOnModelCreating方法中创建关系以创建(例如)关于每个顾问、客户、计划和项目的假设、依赖关系、问题、风险和任务的集合。

我已经尝试了很多中间表/hasone/hasmany 代码示例,以至于我现在完全迷失了......非常感谢任何帮助。

标签: entity-frameworkentity-framework-core

解决方案


听起来这将是继承的一个案例。EF Core 支持 TPH (Table-per-hierarchy),对于这个目的应该没问题。而不是“ParentType”表,而是定义一个基本类型来表示咨询、客户、计划和项目。实体和表将包含所有这些,并表示任务等相关细节的 FK。

即父容器

public abstract class ParentContainer
{
    [Key]
    public int ParentContainerId { get; set; }

    public virtual ICollection<Assumption> Assumptions {get; set;} = new List<Assumption>();
    public virtual ICollection<Dependency> Dependencies {get; set;} = new List<Dependency>();
    public virtual ICollection<Issue> Issues {get; set;} = new List<Issue>();
    public virtual ICollection<Risk> Risks {get; set;} = new List<Risk>();
    public virtual ICollection<Task> Tasks {get; set;} = new List<Task>();
    // ...
}

然后是具体的类:

public class Company : ParentContainer
{}

public class Client : ParentContainer
{}

public class Programme : ParentContainer
{}

public class Project : ParentContainer
{}

这些可以使用鉴别器配置,可以是字符串或类似枚举(Int)的东西,以便 ParentContainer 记录将包含一个列来指示它们是客户端还是程序等。

这种方法的局限性在于 TPH 意味着不同“类型”的所有列最终都包含在同一个表中。这意味着,如果您有适用于客户端和程序的各种不同列,这些列最终都将成为 ParentContainer 表上的可空列。如果您需要为一种或多种类型添加大量额外的列,为了帮助避免这种情况,我建议采用组合模型。所有类型共有的属性(例如“名称”)将驻留在 ParentContainer 表中。特定类型将被移动到特定类型的详细信息表中。

例如,公司和客户可能有地址,而程序和项目则没有。一个计划和项目可能有一个协调员。每个可能有许多非常具体的领域。我们做得到:

public class Company : ParentContainer
{
    public string AddressLine1 { get; set; } // Could simply be AddressId, for demonstration purposes...
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string PostCode { get; set; }
    public string Country { get; set; } 
}

public class Client : ParentContainer
{
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string PostCode { get; set; }
    public string Country { get; set; }
}

public class Programme : ParentContainer
{
    public virtual Person Coordinator { get; set; }
    public string Category { get; set; }
}

public class Project : ParentContainer
{
    public virtual Person Coordinator { get; set; }
    public string ProjectCriteria { get; set;}
}

然而最终会发生的是,所有这些字段都需要作为可为空的列添加到 ParentContainer 表中。组合模型看起来更像:

public class Company : ParentContainer
{
   public virtual CompanyDetails Details { get; set; }
}

public class Client : ParentContainer
{
  public virtual ClientDetails Details { get; set; }
}

public class Programme : ParentContainer
{
    public virtual ProgrammeDetails Details { get; set; }
}

public class Project : ParentContainer
{
    public virtual ProjectDetails Details { get; set; }
}

public class CompanyDetails
{
    [Key]
    public int CompanyDetailsId { get; set; }
    public string AddressLine1 { get; set; } // Could simply be AddressId, for demonstration purposes...
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string PostCode { get; set; }
    public string Country { get; set; } 
}

public class ClientDetails
{
    [Key]
    public int ClientDetailsId { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string PostCode { get; set; }
    public string Country { get; set; }
}

public class ProgrammeDetails
{
    [Key]
    public int ProgrammeDetailsId { get; set; }
    public virtual Person Coordinator { get; set; }
    public string Category { get; set; }
}

public class ProjectDetails
{
    [Key]
    public int ProjectDetailsId { get; set; }
    public virtual Person Coordinator { get; set; }
    public string ProjectCriteria { get; set;}
}

这将看到 ParentContainer 类包含 4 个可空 FK,用于 4 个详细信息引用以及任何“公共”列。类型特定字段在详细信息表中。这允许您自行决定这些列是必需的还是可选的,并且您还可以使用适用的 *DetailsId 作为其他表与特定类型之一之间的其他一对多或多对多关系的 FK,而不是比父容器。从技术上讲,这在 ParentContainer 和详细信息之间形成了多对一的关系,并且从技术上讲,没有什么可以阻止多个容器引用同一 Detail 行,或者不止一个详细信息。该限制需要由应用程序强制执行,您可以使用运行状况检查查询来检测任何非法偏差。(由于错误或不正确的数据端更新)

使用这种方法的问题是,您最终将拥有 ParentContainers 的 DbSet,而不是 Company vs. Client 等。您的存储库/服务可以缓解这种情况,并通过利用OfType<T>作为查询的一部分返回具体实例。例如,要获取名称以“N”开头的公司列表:

var query = context.ParentContainers
    .OfType<Company>()
    .Where(x => x.Name.StartsWith("N"));

这还允许您访问特定于类型的详细信息:

var query = context.ParentContainers
    .OfType<Company>()
    .Where(x => x.CompanyDetails.Address.City == "New York");

推荐阅读