c# - EF 6 代码优先为多对多表生成额外表?
问题描述
参考这篇文章的@Ogglas回答,我想问一下EF生成另一个表是否正常?
如果附加表不应该在那里,那么我在这里做错了什么?请赐教。蒂亚!
示例代码:
public class Aggregate
{
public Aggregate()
{
Episodes = new HashSet<Episode>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
...
public virtual ICollection<Episode> Episodes { get; set; }
}
public class Episode
{
public Episode()
{
Aggregates = new HashSet<Aggregate>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
...
public virtual ICollection<Aggregate> Aggregates { get; set; }
}
public class EpisodeAggregate
{
[Key]
[Column(Order = 1)]
[ForeignKey("Episode")]
public Guid EpisodeId { get; set; }
[Key]
[Column(Order = 2)]
[ForeignKey("Aggregate")]
public Guid AggregateId { get; set; }
public virtual Episode Episode { get; set; }
public virtual Aggregate Aggregate { get; set; }
public DateTime Timestamp { get; set; }
}
在我的 DbContext.cs 中:
public DbSet<EpisodeAggregate> EpisodeAggregates { get; set; }
解决方案
你说的对。在关系数据库中,使用联结表解决多对多关系。
对于标准的多对多关系,您无需提及此联结表;virtual ICollection<...>
实体框架通过您使用多对多关系的两侧来识别多对多,并会自动为您创建表。
为了测试我的数据库理论和实体框架,我经常使用带有学校、学生和教师的简单数据库。学校-学生和学校-教师的一对多和教师-学生的多对多。我总是看到教师-学生连接表是自动创建的,无需提及。
然而!
您的接线表不是标准的。标准联结表只有两列: theEpisodeId
和 the AggregateId
。它甚至没有额外的主键。[EpisodeId, AggregateId] 组合已经是唯一的,可以用作主键。
您在表中EpisodeAggregate
有一个额外的列:TimeStamp
。显然,您想知道情节和聚合何时相关。
“当 Episode[4] 与 Aggregate[7] 相关时,给我时间戳”
这使得该表不是标准的连接表。剧集和聚合之间没有多对多的关系。您在情节及其与聚合的关系之间建立了一对多的关系,并且在聚合及其与情节的关系之间建立了类似的一对多关系。
这使得您必须将多对多更改为一对多:
class Episode
{
public Guid Id { get; set; }
// every Episode has zero or more Relations with Aggregates (one-to-many)
public virtual ICollection<EpisodeAggregateRelation> EpisodeAggregateRelations { get; set; }
...
}
class Aggregate
{
public Guid Id { get; set; }
// every Episode has zero or more Relations with Episodes(one-to-many)
public virtual ICollection<EpisodeAggregateRelation> EpisodeAggregateRelations { get; set; }
...
}
class EpisodeAggregateRelation
{
// Every Relation is the Relation between one Episode and one Aggregate
// using foreign key:
public Guid EpisodeId { get; set; }
public Guid AggregateId { get; set; }
public virtual Episode Episode { get; set; }
public virtual Aggregate Aggregate { get; set; }
public DateTime Timestamp { get; set; }
}
如果您确定剧集和聚合之间始终存在至多一种关系,则可以使用组合 [EpisodeId, AggregateId] 作为主键。如果你认为这两个可能有几个关系,你需要添加一个单独的主键。
我经常在不同的数据库中使用我的类,因此我不喜欢属性,我在 OnModelCreating 中用流利的 API 解决它:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Episode>()
.HasKey(episode => episode.Id)
// define the one-to-many with EpisodeAggregateRelations:
.HasMany(episode => episode.EpisodeAggregateRelations)
.WithRequired(relation => relation.Episode)
.HasForeignKey(relation => relation.EpisodeId);
modelBuilder.Entity<Aggregate>()
.HasKey(aggregate => aggregate.Id)
// define the one-to-many with EpisodeAggregateRelations:
.HasMany(aggregate => aggregate .EpisodeAggregateRelations)
.WithRequired(relation => relation.Aggregate)
.HasForeignKey(relation => relation.aggregateId);
以上不需要!
因为您遵循了实体框架代码优先约定,所以可以省略这两条语句。实体框架将识别主键和一对多关系。仅当您想偏离约定时,例如非标准表名,或者如果您想定义列顺序:
modelBuilder.Entity<Episode>()
.ToTable("MySpecialTableName")
.Property(episode => episode.Date)
.HasColumnName("FirstBroadcastDate")
.HasColumnOrder(3)
.HasColumnType("datetime2");
但同样:您遵循约定,不需要所有这些属性,如 Key、ForeignKey、DatabaseGenerated。列顺序:谁在乎?让您的数据库管理系统决定最佳的列顺序。
我的建议是:尝试实验:忽略这个流畅的 API 并检查你的单元测试是否仍然通过。五分钟后检查。
EpisodeAggregateRelation
有一些非标准的东西:它有一个复合主键。因此,您需要定义它。请参见配置复合主键
modelBuilder.Entity<EpisodeAggregateRelation>()
.HasKey(relation => new
{
relation.EpisodId,
relation.AggregateId
});
如果您已经在 Episodes 和 Aggregates 中定义了一对多,或者由于约定不需要这样做,那么您不必在这里再次提及这种关系。
如果需要,您可以将一对多放在 EpisodeAggregateRelation 的 fluent API 部分,而不是在 Episode / Aggregate 的 fluent API 部分:
// every EpisodeAggregateRelation has one Episode, using foreign key
modelBuilder.Entity<EpisodeAggregateRelation>()
.HasRequired(relation => relation.Episode(
.WithMany(episode => episode.EpisodeAggregateRelations)
.HasForeignKey(relation => relation.EpisodeId);
// similar for Aggregate
最后一个提示
不要在构造函数中创建 HashSet。如果您获取数据,则会浪费处理能力:您创建了 HashSet,它会立即被ICollection<...>
框架创建的实体替换。
如果你不相信我:试一试,看看你的单元测试是否通过,除了检查现有单元测试的单元测试之外。ICollection<...>
推荐阅读
- reactjs - 在材料表+ reactjs中进行内联编辑时出错
- sql - Oracle SQL中如何列出每个类别中最贵和最便宜的项目?
- jquery - 如何在 jquery 中对基于 Datatable 的字符串数组进行排序?
- r - R中的贝叶斯套索变量选择
- java - Plugin IntelliJ Java 自动创建代码
- xml - 如何使用具有依赖关系的 xslt 排除 xml 对象
- r - 如何使用 Rmarkdown 制作可变长度报告?
- python - 为什么我的 IronPython WPF 应用程序在没有错误或警告的情况下关闭?
- fossil - 从化石存储库中永久删除提交
- linux - kill(SIGSTOP) 是否在 kill() 返回时生效?