我已经在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
来浏览这个有点繁琐的层次结构。为了从PersonId
到Club
的集合,您可以使用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"),
}
我经历过,一旦我开始在生成的集合中思考我想要的联接,而不是我需要的联接来获得这些集合,我发现我几乎不使用联接。一对多关系和多对多关系都是这种情况。实体框架将在内部使用正确的联接。