Fluent API, Many-To-Many in Entity Framework Core

Fluent API, many-to-many in Entity Framework Core

EF Core 5.0 RC1+

As of EF Core 5.0 RC1, it's possible to do this without an explicit join table. EF Core is able to configure a mapping for the many-to-many relationship shown in your question without requiring you to create a PersonClub type.

See What's New in EF Core 5.0, RC1, Many-to-many in the official docs for more information.

Previous Versions

This is not yet possible in EF Core without using an explicit class for the join. See here for an example of how to do that.

There's an open issue on Github asking for the ability to do this without the need for an explicit class, but it has not yet been completed.

Using your scenario, the example I linked would recommend the following entity classes:

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

The following OnModelCreating would then be used for setup:

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

Be sure to go to the open issue I linked and voice your frustration if you feel the need.

EDIT: The open issue suggests using a simple Select to navigate through this somewhat cumbersome hierarchy. In order to get from a PersonId to a collection of Clubs, you can use SelectMany. e.g.:

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

I can't vouch for whether this is truly a "best practice", but it should certainly do the trick and I think its fair to say it's not overly ugly.

ManyToMany Relation in EF Core fluent API

You need to use some of the other UsingEntity overloads which allow you to configure the left and right navigations.

For instance

entityTypeBuilder1
.HasMany(lambdaManyFirst)
.WithMany(lambdaManySecond)
.UsingEntity<Dictionary<string, object>>(joinEntityName,
j => j.HasOne<TEntity2>().WithMany().HasForeignKey(fkName2),
j => j.HasOne<TEntity1>().WithMany().HasForeignKey(fkName1),
j => j.ToTable(joinTableName)
);

EF Core 5 Many-to-Many relation - Change the Navigation key

It was achieved by adding the .HasForeignKey() in usingEntity

builder.Entity<Framework>()
.HasMany(x => x.Paths)
.WithMany(x => x.LinkedFrameworks) //Change the name here
.UsingEntity<Dictionary<string, object>>(
"FrameworkPaths",
x => x.HasOne<Path>().WithMany(),
x => x.HasOne<Framework>().WithMany().HasForeignKey("FrameworksId")); //tell the EF about the Foreign Key

EF Core 5.0 Adding many-to-many makes one-to-many unable to determine

Multiple relationships between two entities usually cannot be determined automatically and need configuration. Many things (and especially relationships related) in EF Core can only be configured only with fluent API.

So rather than looking for non existing or non intuitive data annotations, simply configure the desired relationships (at minimum - navigations properties and cardinality) fluently:

modelBuilder.Entity<Notification>(builder =>
{
builder.HasOne(e => e.Creator).WithMany();
builder.HasOne(e => e.Updater).WithMany();
builder.HasMany(e => e.Recipients).WithMany(e => e.Notifications);
});

EntityFramework Core 3.0 Fluent API Many-to-many relationship builder creating extra foreign key columns

EDIT

As per @Flater's comment the reason for this was that the configuration was specified twice thus an extra set of columns was created.


Actually removing the duplex one-to-many Fluent API configuration from the ThreadsParticipants entity type configuration solves this issue and the extra properties are no longer created. When this portion of the config is removed
EF uses naming convetions to figure the relationship out so there's no need to explicitly state the relationship details.

    builder
.HasOne(tp => tp.ThreadModel)
.WithMany()
.HasForeignKey(tp => tp.ThreadModelId)
.IsRequired();

builder
.HasOne(tp => tp.ParticipantModel)
.WithMany()
.HasForeignKey(tp => tp.ParticipantModelId)
.IsRequired();

This leads me to think that there is a property name mismatch that is causing all the trouble.



Related Topics



Leave a reply



Submit