Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can not build relationships when attach disconnected entities which have many-to-many related entities #23787

Closed
anranruye opened this issue Dec 31, 2020 · 3 comments
Labels
area-change-tracking closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-bug
Milestone

Comments

@anranruye
Copy link

anranruye commented Dec 31, 2020

File a bug

EF Core version: 5.0.1, 6.0.0-alpha.1.20622.2(daily build)
Database provider: Sqlite
Target framework: .NET 5.0
Operating system: Windows 10
IDE: Visual Studio 2019 16.8.1

Consider we have such entities and DbContext:

    public class Post
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public List<Tag> Tags { get; set; }

        public List<PostTag> PostTags { get; set; }
    }

    public class Tag
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public List<Post> Posts { get; set; }

        public List<PostTag> PostTags { get; set; }
    }

    public class PostTag
    {
        public int PostId { get; set; }

        public int TagId { get; set; }

        public Post Post { get; set; }

        public Tag Tag { get; set; }
    }

    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }

        public DbSet<Post> Posts { get; set; }

        public DbSet<Tag> Tags { get; set; }

        public DbSet<PostTag> PostTags { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Post>().HasMany(x => x.Tags)
                .WithMany(x => x.Posts)
                .UsingEntity<PostTag>(
                    j => j.HasOne(x => x.Tag).WithMany(x => x.PostTags).HasForeignKey(x => x.TagId),
                    j => j.HasOne(x => x.Post).WithMany(x => x.PostTags).HasForeignKey(x => x.PostId)
                );
        }
    }

If we want to remove all tags from a post, we expect all these methods should work, but actually not:

    //method1, this works
    var post = db.Posts.Include(x => x.PostTags).FirstOrDefault();
    post.PostTags.Clear();

    db.SaveChanges();

    //method2, this works, equivalent to method1
    var post = db.Posts.AsNoTracking().Include(x => x.PostTags).FirstOrDefault();
    db.Attach(post); 
    post.PostTags.Clear();

    db.SaveChanges();

    //method3, this works
    var post = db.Posts.Include(x => x.Tags).FirstOrDefault();
    post.Tags.Clear();

    db.SaveChanges();

    //method4, this doesn't work, should be equivalent to method3 but not
    var post = db.Posts.AsNoTracking().Include(x => x.Tags).FirstOrDefault();
    db.Attach(post);
    post.Tags.Clear();

    db.SaveChanges();

    //method5, this works
    var post = db.Posts.AsNoTracking().FirstOrDefault();
    db.Attach(post).Collection(p => p.Tags).Load();
    post.Tags.Clear();

    db.SaveChanges();

    //method6, this doesn't work
    var post = db.Posts.AsNoTracking().Include(x => x.Tags).FirstOrDefault();
    db.Attach(post).Collection(p => p.Tags).Load();
    post.Tags.Clear();

    db.SaveChanges();

    //method7, tags are removed except the first one
    var post = db.Posts.AsNoTracking().Include(x => x.Tags).FirstOrDefault();
    post.Tags = post.Tags.Take(1).ToList();
    db.Attach(post).Collection(p => p.Tags).Load();
    post.Tags.Clear();

    db.SaveChanges();

Related tags of a disconnected post are tracked but not considered related to the post when attach the post to a DbContext.

@gojanpaolo
Copy link

gojanpaolo commented Jan 1, 2021

I think I'm getting into a similar issue where Attaching an entity results in unexpected behavior when after SaveChanges...

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using Xunit;

public class Tests
{
    [Fact]
    public void Test()
    {
        using (var ctx = new MyContext())
        {
            ctx.Database.EnsureDeleted();
            ctx.Database.EnsureCreated();
            ctx.Attach(Cuisine.Chinese);
            ctx.Add(new Restaurant { Cuisines = { Cuisine.Chinese } });
            ctx.SaveChanges();
        }

        using (var ctx = new MyContext())
        {
            ctx.Attach(Cuisine.Chinese);

            var restaurant = ctx.Set<Restaurant>().First();

            Assert.NotEmpty(restaurant.Cuisines); // pass

            restaurant.Cuisines.Clear();

            Assert.Empty(restaurant.Cuisines); // pass

            ctx.SaveChanges(); // save changes unexpectedly adds back the cuisines

            Assert.Empty(restaurant.Cuisines); // fail
        }
    }
    public class MyContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer("server=(localdb)\\mssqllocaldb;database=test");
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Restaurant>().HasMany(_ => _.Cuisines).WithMany(_ => _.Restaurants);
            modelBuilder.Entity<Cuisine>().HasData(Cuisine.Chinese);
        }
    }
    public sealed class Restaurant
    {
        public int RestaurantId { get; set; }
        public ICollection<Cuisine> Cuisines { get; } = new HashSet<Cuisine>();
    }
    public sealed class Cuisine
    {
        public static readonly Cuisine Chinese = new Cuisine { CuisineId = 1 };
        public int CuisineId { get; set; }
        public ICollection<Restaurant> Restaurants { get; } = new HashSet<Restaurant>();
    }
}

@anranruye anranruye changed the title Can not build relationships when attach disconnected entities with many-to-many related entities Can not build relationships when attach disconnected entities which have many-to-many related entities Jan 1, 2021
@ajcvickers
Copy link
Member

Confirmed this is a bug. Attach is not leaving the relationship state of entities set correctly. A workaround is to make a call to DetectChanges immediately after calling Attach. For example:

var post = db.Posts.AsNoTracking().Include(x => x.Tags).FirstOrDefault();
db.Attach(post);
db.ChangeTracker.DetectChanges();

post.Tags.Clear();

db.SaveChanges();

@gojanpaolo
Copy link

gojanpaolo commented Jan 2, 2021

@ajcvickers Thanks for workaround which passes the test on my previous example.
It looks like I have another issue which is because of how I have static instances of my readonly Cuisine entity and it has a ICollection<Restaurant>. The Restaurant instances loaded in that collection on one DbContext instance causes all sorts of issues on the next DbContext instance.
Will those issues get covered with #3864 and I remove the ICollection<Restaurant> property?
Are there any possible workarounds so I can keep my static instances and still leverage many-to-many? If none, I'll probably revert to having a join entity class.

Oh and Happy New Year to the EF Core team! 🥳

@ajcvickers ajcvickers added this to the 5.0.3 milestone Jan 4, 2021
ajcvickers added a commit that referenced this issue Jan 4, 2021
… with generated key values

This is a precursor to fixes for #23659, #23787. This test has no product changes, it just refactors the many-to-many tests so that they can be run with generated key values, as well as running with explicit keys values like they currently do. Generated keys result in more work being done in fixup by navigations, which is where both of these issues live.

Once this is merged I will send out separate PRs to fix the two bugs.
ajcvickers added a commit that referenced this issue Jan 5, 2021
… with generated key values (#23807)

This is a precursor to fixes for #23659, #23787. This test has no product changes, it just refactors the many-to-many tests so that they can be run with generated key values, as well as running with explicit keys values like they currently do. Generated keys result in more work being done in fixup by navigations, which is where both of these issues live.

Once this is merged I will send out separate PRs to fix the two bugs.
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jan 5, 2021
This was referenced Mar 15, 2021
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-change-tracking closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported Servicing-approved type-bug
Projects
None yet
Development

No branches or pull requests

3 participants