Fluent API,Entity Framework Core 中的多对多

Posted

技术标签:

【中文标题】Fluent API,Entity Framework Core 中的多对多【英文标题】:Fluent API, many-to-many in Entity Framework Core 【发布时间】:2018-02-21 09:44:26 【问题描述】:

我已经在 *** 中搜索了使用 EF Core、代码优先和 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; 

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

【问题讨论】:

【参考方案1】:

EF Core 5.0 RC1+

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

有关详细信息,请参阅官方文档中的What's New in EF Core 5.0, RC1, Many-to-many。

以前的版本

如果不使用显式连接类,这在 EF Core 中是不可能的。有关如何执行此操作的示例,请参阅 here。

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

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

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 获取Clubs 的集合,您可以使用SelectMany。例如:

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

我不能保证这是否真的是一个“最佳实践”,但它肯定可以解决问题,而且我认为它并不过分丑陋。

【讨论】:

迁移到 5.0 很简单,而且效果很好 你能提供ClubsController方法作为例子吗?我需要类似 [EnableQuery] public IActionResult Get() return Ok(_dbContext.Clubs .Include(x => x.PersonClubs) .ThenInclude(x => x.Persons)); 但这不起作用 [OfftopicAttribute] /* 这是我一生中得到的最好答案之一,尤其是因为我正在接受 .Net 培训,今天课程项目的截止日期没有工作数据库,它可能只是救了我*/【参考方案2】:

正确的“设置”是:

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

【讨论】:

如果在链接实体中使用 [ForeignKey] 属性,则不需要任何代码。即便如此:可悲的是,这不像早期版本那样工作。在 4 年的 Core 开发中,他们无法做出与之前版本相匹配的内容。 没必要不代表不正确。在配置中显式并没有什么坏处,实际上很多时候有助于避免由隐式 EF Core 约定引起的意外。有人要求提供一种摆脱所有约定并要求对所有内容进行显式配置的方法。 PersonClub 的流畅映射怎么样? 如何根据 PersonClub 内部的人员和信息正确检索所有俱乐部。喜欢 Person.Clubs?【参考方案3】:

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

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

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

幸运的是,您不需要在查询中提及这个额外的表格。

如果您需要类似“给我所有 'Clubs' ......来自每个 'Person' ......”这样的查询,不要在联接中思考。而是使用 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),
    ;

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

反过来:让所有乡村俱乐部都有他们的“John Doe”人物:

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"),
    

我曾经经历过,一旦我开始考虑我想要的结果集合而不是我需要获取这些集合的连接,我发现我几乎不使用连接。一对多关系和多对多关系都是这种情况。实体框架将在内部使用正确的连接。

【讨论】:

以上是关于Fluent API,Entity Framework Core 中的多对多的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 4.1 Fluent API 属性

Entity Framework Code First Fluent API - 配置关系

Entity Framework Configuring Relationships with the Fluent API

如何通过 Fluent API Entity Framework 定义多对多关系?

Entity Framework Code First Fluent Api:向列添加索引

使用 Entity Framework Fluent API 的一对一可选关系