Fluent API,实体框架核心中的多对多



我已经在stackoverflow中搜索了使用EF Core,Code First和Fluent API生成多对多关系的适当解决方案。

一个简单的场景是:

public class Person
{
public Person() {
Clubs = new HashSet<Club>();
}
public int PersonId { get; set; }
public virtual ICollection<Club> Clubs { get; set; }
}
public class Club
{
public Club() {
Persons = new HashSet<Person>();
}
public int ClubId { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}

如果我错了,请纠正我,但老实说,我找不到一个包含如何使用所述工具执行此操作的详细解释的问题。 谁能解释一下这是如何完成的?

EF Core 5.0 RC1+

从 EF Core 5.0 RC1 开始,可以在没有显式联接表的情况下执行此操作。EF Core 能够为问题中显示的多对多关系配置映射,而无需创建PersonClub类型。

有关详细信息,请参阅官方文档中的 EF Core 5.0、RC1、多对多中的新增功能。

以前的版本

如果不使用显式类进行联接,这在 EF Core 中尚无法实现。有关如何执行此操作的示例,请参阅此处。

Github上有一个未解决的问题,要求能够在不需要显式类的情况下执行此操作,但尚未完成。

使用您的方案,我链接的示例将推荐以下实体类:

public class Person
{
public int PersonId { get; set; }
public virtual ICollection<PersonClub> PersonClubs { get; set; }
}
public class Club
{
public int ClubId { get; set; }
public virtual ICollection<PersonClub> PersonClubs { get; set; }
}
public class PersonClub
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int ClubId { get; set; }
public Club Club { get; set; }
}

然后,以下OnModelCreating将用于设置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonClub>()
.HasKey(pc => new { pc.PersonId, pc.ClubId });
modelBuilder.Entity<PersonClub>()
.HasOne(pc => pc.Person)
.WithMany(p => p.PersonClubs)
.HasForeignKey(pc => pc.PersonId);
modelBuilder.Entity<PersonClub>()
.HasOne(pc => pc.Club)
.WithMany(c => c.PersonClubs)
.HasForeignKey(pc => pc.ClubId);
}

请务必转到我链接的未解决的问题,并在必要时表达您的挫败感。

编辑:未解决的问题建议使用一个简单的Select来浏览这个有点繁琐的层次结构。为了从PersonIdClub的集合,您可以使用SelectMany.例如:

var clubs = dbContext.People
.Where(p => p.PersonId == id)
.SelectMany(p => p.PersonClubs);
.Select(pc => pc.Club);

我不能保证这是否真的是一种"最佳实践",但它肯定应该可以解决问题,我认为公平地说它并不过分丑陋。

正确的"设置"是:

public class Person
{
public int PersonId { get; set; }
public virtual ICollection<PersonClub> PersonClubs { get; set; }
}
public class Club
{
public int ClubId { get; set; }
public virtual ICollection<PersonClub> PersonClubs { get; set; }
}
public class PersonClub
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int ClubId { get; set; }
public Club Club { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonClub>()
.HasKey(pc => new { pc.PersonId, pc.ClubId });
}

因此,这个用于配置"胶水表"的块不是必需的,如@Kirk示例所示:

modelBuilder.Entity<PersonClub>()
.HasOne(pc => pc.Person)
.WithMany(p => p.PersonClubs)
.HasForeignKey(pc => pc.PersonId);
modelBuilder.Entity<PersonClub>()
.HasOne(pc => pc.Club)
.WithMany(c => c.PersonClubs)
.HasForeignKey(pc => pc.ClubId);

所以每个Person都有零个或多个Clubs,每个Club都有零个或多个Persons。正如您正确指出的,这是一个适当的多对多关系。

您可能知道关系数据库需要一个额外的表来实现这种多对多关系。实体框架的好处是它可以识别这种关系并为您创建此额外的表。

乍一看,这个额外的表不是您DbContext中的dbSet似乎是一个问题:"如果我没有DbSet,如何执行与这个额外表的连接?

幸运的是,您无需在查询中提及此额外表。

如果您需要像"给我所有'俱乐部'这样的查询......来自每个"人"谁..."不要在连接中思考。而是使用ICollections!

让所有"John Doe"人与他们参加的所有乡村俱乐部:

var result = myDbContext.Persons
.Where(person => person.Name == "John Doe")
.Select(person => new
{
PersonId = person.Id,
PersonName = person.Name,
AttendedCountryClubs = person.Clubs
.Where(club => club.Type = ClubType.CountryClub),
};

实体框架将识别出需要与额外的多对多表进行联接,并将执行此联接,而无需提及此额外表。

反之亦然:让所有乡村俱乐部都有他们的"约翰·多伊"人物:

var result = myDbContext.Clubs
.Where(club => club.Type = ClubType.CountryClub)
.Select(club => new
{
ClubId = club.Id,
ClubName = club.Name,
AnonymousMembers = club.Persons
.Where(person => person.Name == "John Doe"),
}

我经历过,一旦我开始在生成的集合中思考我想要的联接,而不是我需要的联接来获得这些集合,我发现我几乎不使用联接。一对多关系和多对多关系都是这种情况。实体框架将在内部使用正确的联接。

相关内容

  • 没有找到相关文章

最新更新