问题描述
参考此@Ogglas answer的post, 我想问问EF生成另一个表是否正常?
如果附加表不应该在那儿,那我在做什么错呢?请赐教。 TIA!
示例代码:
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<...>
来识别多对多,并将自动为您创建表。
为了测试我的数据库理论和实体框架,我经常使用带有学校,学生和教师的简单数据库。学校学生和学校老师一对多,教师学生一对多。我总能看到教师与学生的连接表是自动创建的,而无需提及它。
但是!
您的接线表不是标准的。标准联结表只有两列:EpisodeId
和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);
不需要以上内容!
由于遵循了entity framework code first conventions,因此可以省略这两个语句。实体框架将识别主键和一对多关系。仅当您想要偏离约定(例如非标准表名)或要定义列顺序时:
modelBuilder.Entity<Episode>()
.ToTable("MySpecialTableName")
.Property(episode => episode.Date)
.HasColumnName("FirstBroadcastDate")
.HasColumnOrder(3)
.HasColumnType("datetime2");
但是再次:您遵循约定,不需要所有这些属性,例如Key,ForeignKey和DatabaseGenerated。和列顺序:谁在乎?让您的数据库管理系统决定最佳的列顺序。
我的建议是:尝试进行实验:忽略此流利的API,并检查您的单元测试是否仍然通过。五分钟内签到。
EpisodeAggregateRelation
有一些非标准的内容:它有一个复合主键。因此,您需要定义此。参见Configuring a composite primary key
modelBuilder.Entity<EpisodeAggregateRelation>()
.HasKey(relation => new
{
relation.EpisodId,relation.AggregateId
});
如果您已经在“情节和汇总”中定义了“一对多”,或者由于约定而不需要,则不必在这里再次提及这种关系。
如果需要,您可以将一对多放在EpisodeAggregateRelation的流畅API部分中,而不是放在Episode / Aggregate的流畅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<...>
的单元测试的可能例外