EF 6 代码优先为许多 table 生成额外的 table?

EF 6 code-first generating extra table for many-many table?

引用自 @Ogglas answer of this post, 请问EF再生成一个table是否正常?

如果附加的 table 不应该存在,那我做错了什么?请赐教。 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; }

你是对的。在关系数据库中,使用联结 table.

解决多对多关系

对于标准的多对多关系,你不需要提到这个连接点table; entity framework 通过在多对多关系的两边使用 virtual ICollection<...> 来识别多对多,并会自动为您创建 table。

为了测试我的数据库理论和 entity framework,我经常使用包含学校、学生和教师的简单数据库。学校-学生和学校-教师一对多,教师-学生多对多。我总是看到 Teacher-Student junction table 是自动创建的,无需提及它。

不过!

您的连接 table 不标准。标准联结 table 只有两列:EpisodeIdAggregateId。它甚至没有额外的主键。组合 [EpisodeId, AggregateId] 已经是唯一的,可以用作主键。

您在 table EpisodeAggregate 中有一个额外的列:TimeStamp。显然你想知道 Episode 和 Aggregate 何时相关。

“给我 Episode[4] 与 Aggregate[7] 关联时的时间戳”

这使得这个 table 不是标准的交汇点 table。 Episodes 和 Aggregates 之间没有多对多关系。您在 Episode 及其与聚合的关系之间建立了一对多关系,同样在聚合及其与 Episodes 的关系之间建立了一对多关系。

这使得您必须将多对多更改为一对多:

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; }
}

如果您确定 Episode 和 Aggregate 之间始终最多只有一种关系,则可以使用组合 [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,你可以省略这两个语句。 Entity framework 将识别主键和一对多关系。仅当您想要偏离约定时,例如非标准 table 名称,或者如果您想要定义列顺序:

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
});

如果您已经在 Episodes 和 Aggregates 中定义了一对多关系,或者如果由于约定不需要它,则不必在这里再次提及这种关系。

如果需要,您可以将一对多放在 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,它立即被 entity framework 创建的 ICollection<...> 所取代。

如果您不相信我:请尝试一下,看看您的单元测试是否通过,检查现有 ICollection<...>

的单元测试可能除外