Search code examples
c#scaffoldingef-core-6.0

Alternatives to using join entities now that EF Core 6.0 DBContext scaffolding no longer maps simple join tables to explicit entity types


I am currently upgrading a large database-first project to EF Core 6.0. Details of the breaking EF Core 6.0 scaffolding change described in the title can be found here: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/breaking-changes#many-to-many. I am also aware of the mitigations available and the open efcore issue to optionally restore join tables.

From the article above:

It is very rare that the join entity type needs to be used directly when it contains only two foreign keys for the many-to-many relationships.

This is simply not true in the project I am working on. My questions are:

  • Why is the preferred mitigation to use the many-to-many relationships directly?
  • What is the recommended way of doing so when adding relationships between existing entities (example below)?
  • Is there a more performant approach, or is this a question of style?

For example, consider a many-to-many relationship between a Person and a Thing. In EF Core 6.0 the join entity is no longer available:

public partial class PersonThing
{
    public int PersonId { get; set; }    
    public int ThingId { get; set; }

    public virtual Person Person { get; set; }
    public virtual Thing Thing { get; set; }
}

And the tables are mapped with a many-to-many relationship instead:

public partial class Person
{
    public Person()
    {
        Thing = new HashSet<Thing>();
    }

    public int PersonId { get; set; }        
    public string PersonName { get; set; }    

    public virtual ICollection<Thing> Thing { get; set; }
}

public partial class Thing
{
    public Thing()
    {        
        Person = new HashSet<Person>();
    }

    public int ThingId { get; set; }
    public string ThingName { get; set; }    

    public virtual ICollection<Person> Person { get; set; }
}

Previously it was possible to update the relationship between existing Persons and Things using ids only:

public void UpdateThings(IReadOnlyCollection<int> thingIds)
{
    // sync PersonThings
    var syncQueue = SynchronisationHelper.GetSynchronisationQueue(thingIds, _model.PersonThing, (s, d) => s == d.ThingId);
    
    foreach (var thingId in syncQueue.Inserts)
    {
        var personThing = new Database.Model.PersonThing
        {
            PersonId = _model.PersonId,            
            ThingId = thingId
        };
        _model.PersonThing.Add(personThing);            
    }
    
    foreach (var personThing in syncQueue.Deletes)
    {            
        _context.PersonThing.Remove(personThing);
    }
}

What is the alternative to this without access to the join entity type? Must you load the related entity from context just to create the relationship, or is there a better way of achieving this?

public void UpdateThings(IReadOnlyCollection<int> thingIds)
{
    // sync Things
    var syncQueue = SynchronisationHelper.GetSynchronisationQueue(thingIds, _model.Thing, (s, d) => s == d.ThingId);
    foreach (var thingId in syncQueue.Inserts)
    {
        var thing = _context.Thing.Single(t => thingId == t.ThingId);
        _model.Thing.Add(thing);
    }

    foreach (var thing in syncQueue.Deletes)
    {
        _model.Thing.Remove(thing);
    }
}

Solution

  • Based on this comment on the open efcore issue to optionally restore join tables, I am satisfied that using join entity types is a perfectly valid database-first approach, and using them (or not) is a question of preference / style. There are already mitigations available to map join entity types using scaffolding in EF Core 6.0, hopefully the issue is addressed so this can be achieved without a workaround.