dotnet / dotnet/efcore

Make changing many-to-many collections easier

Open
#27,677 3 comments 4 reactions 0 assignees View on GitHub
area-change-tracking customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

I have a ManyToMany relationship that I want to update. My steps to update are:

1. Load entity from Db, including the related collection
2. Clear the collection
3. Add all the related entities back in from a disconnected entity (the updated state from the frontend)

This works with a OneToMany relationship. Which surprised me, because I expected the familiar "entity with the same key value is already being tracked" exception.

I know how to solve my particular problem, but I'm curios if there is a way to make the "just clear and add the new state" method work on ManyToMany relationships?
Am I even supposed to use this method on OneToMany relationships or does it have any bad effects?
I saw that it fires a full UPDATE statement for every child and every field. Not optimal, but if I know about this behavior I can decide if the simpler code is worth it for me.
I would have expected this drawback to not even exist for the ManyToMany case, since its only updating/creating the joinEntity.

I created a fully reproducible sample.
```
// One To Many works
using (var seedContext = new Context())
{
seedContext.Database.Migrate();

var parentWithChild = new Parent();
parentWithChild.Children.Add(new Child());
seedContext.Add(parentWithChild);
await seedContext.SaveChangesAsync();
}

using (var updateContext = new Context())
{
var disconnectedParent = updateContext.Parents
.AsNoTracking()
.Include(x => x.Children)
.First();

var trackedParent = updateContext.Parents
.Include(x => x.Children)
.First();

trackedParent.Children.Clear();
trackedParent.Children.AddRange(disconnectedParent.Children);

var entries = updateContext.ChangeTracker.Entries().ToList();
// = 3 Entries (1 Parent Unchanged, 1 Child (shared) Deleted, 1 Child (shared) Modified
}

// Many To Many does not work
using (var seedContext = new Context())
{
var post = new Post();
post.Tags.Add(new Tag());
seedContext.Add(post);
await seedContext.SaveChangesAsync();
}

using (var updateContext = new Context())
{
var disconnectedPost = updateContext.Posts
.AsNoTracking()
.Include(x => x.Tags)
.First();

var trackedPost = updateContext.Posts
.Include(x => x.Tags)
.First();

trackedPost.Tags.Clear();
trackedPost.Tags.AddRange(disconnectedPost.Tags);

var entries = updateContext.ChangeTracker.Entries().ToList();
// crashes with "Entity with the same key is already being tracked" Exception
}

public class Context : DbContext
{
public DbSet Parents { get; set; }
public DbSet Posts { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connStr = new SqliteConnectionStringBuilder { DataSource = $@"file:db?mode=memory&cache=shared" }.ConnectionString;
optionsBuilder.UseSqlite(connStr);
base.OnConfiguring(optionsBuilder);
}
}

public class Parent
{
public Guid Id { get; set; }
public List Children { get; set; } = new();
}

public class Child
{
public Guid Id { get; set; }
public Parent Parent { get; set; }
public Guid ParentId { get; set; }
}

public class Post
{
public Guid Id { get; set; }
public List Tags { get; set; } = new();
}

public class Tag
{
public Guid Id { get; set; }
public List Posts { get; set; } = new();
}
```

### Include provider and version information

EF Core version: 6.0.3
Database provider: Sqlite

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.