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

问题描述

参考此@Ogglas answerpost, 我想问问EF生成一个表是否正常?

enter image description here

如果附加表不应该在那儿,那我在做什么错呢?请赐教。 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<...>来识别多对多,并将自动为您创建表。

为了测试我的数据库理论和实体框架,我经常使用带有学校,学生和教师的简单数据库。学校学生和学校老师一对多,教师学生一对多。我总能看到教师与学生的连接表是自动创建的,而无需提及它。

但是!

您的接线表不是标准的。标准联结表只有两列:EpisodeIdAggregateId。它甚至没有额外的主键。组合[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<...>的单元测试的可能例外