entity-framework - EF Core 循环往复 - 不同父母的多个实体集合
问题描述
一直在花费大量时间谷歌搜索并寻找解决方案,但没有更接近。
我有一系列对象:
- 假设
- 依赖项
- 问题
- 风险
- 任务...等(有 c15 对象类型)
目前它们都被定义为简单的类——我将在稍后创建每个类的细节:
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 代码示例,以至于我现在完全迷失了......非常感谢任何帮助。
解决方案
听起来这将是继承的一个案例。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");
推荐阅读
- c++ - C++ 错误:表达式不能用作函数
- python - Cassandra ResultSet 使用分页返回相同的记录集(相同的页面)
- symfony - 如何使用 Doctrine 在 Symfony 5 中保存外键 OneToMany 关系
- ios - 在 iOS 上使用多项选择应用程序实施多项问题的建议
- javascript - 通过flask app.route动态分配dash.layout函数
- javascript - 如何知道我们是否正在离开网页?
- python - Python PIL - 圆角多边形
- ios - 如何应用自定义样式 XF.Material 控件
- javascript - 如何使用 forEach 生成连接字符串
- google-cloud-pubsub - 使用 Google Pub/Sub Java 客户端库而不添加 GOOGLE_APPLICATION_CREDENTIALS 环境变量进行身份验证