首页 > 解决方案 > 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; }

标签: c#entity-frameworkentity-framework-6

解决方案


你说的对。在关系数据库中,使用联结表解决多对多关系。

对于标准的多对多关系,您无需提及此联结表;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<...>


推荐阅读