Skip to content

Entity already tracked exception for entity with multiple references to same navigation property #20116

@vflame

Description

@vflame

When trying to attach/update an entity with multiple references to the same entity (and included with Include/ThenInclude), EF core throws an exception indicating the entity is already tracked.

For example:

  • A rental have a navigation to a User.
  • A rental has multiple reservations.
  • Each reservation has a navigation to the same User as the parent rental.

I can work around this by using a new context to materialize the entity and then making the changes on that entity; or using context.Entry(rental).Reload(); however, is it possible to context.Attach or context.Update disconnected entities with multiple references to a same entity? Why or why not?

Steps to reproduce

    class Program
    {
        static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            var ctx = host.Services.GetRequiredService<MyContext>();
            ctx.Database.Migrate();

            //seed data
            var seedUser = new User()
            {
                Id = "10",
                UserName = "SeedUser"
            };
            var seedReservation = new ReservationSlot()
            {
                Id = "15",
                User = seedUser
            };
            var seedRental = new Rental()
            {
                User = seedUser,
                Id = "5",
                Modified = DateTime.Now,
                Reservations = new List<ReservationSlot>() { seedReservation }
            };
            ctx.Rentals.Add(seedRental);
            ctx.SaveChanges();
            ctx.Dispose();

            var newCtx1 = host.Services.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider.GetRequiredService<MyContext>();

            //materialize disconnected entity
            var rentals = newCtx1.Rentals
                .Include(p => p.User)
                .Include(p => p.Reservations)
                    .ThenInclude(p => p.User).AsNoTracking().ToList();

            newCtx1.Dispose();



            var rental = rentals.First();
            //attach disconnected entity
            var newCtx2 = host.Services.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider.GetRequiredService<MyContext>();

            //exception thrown for entity "User" being tracked more than once.
            newCtx2.Attach(rental);

            rental.Modified = DateTime.Now;
            newCtx2.SaveChanges();

            Console.ReadKey();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddDbContext<MyContext>(o => o.UseSqlite("Data Source=blogging.db"));
            });

    }

    public class MyContext : DbContext
    {
        public MyContext(DbContextOptions<MyContext> options) : base(options)
        {
        }
        public DbSet<Rental> Rentals { get; set; }
    }
    public class Rental
    {
        public string Id { get; set; }
        public List<ReservationSlot> Reservations { get; set; }

        public User User { get; set; }
        public DateTime Modified { get; set; }
    }

    public class ReservationSlot
    {
        public string Id { get; set; }
        public User User { get; set; }
    }

    public class User
    {
        public string Id { get; set; }
        public string UserName { get; set; }
    }

Alternatively, trying to seed the data with multiple references to the same User also raises the exception:

           //seed data
            var seedUser = new User()
            {
                Id = "10",
                UserName = "SeedUser"
            };            

            var seedReservation1 = new ReservationSlot()
            {
                Id = "15",
                User = seedUser
            };
            var seedReservation2 = new ReservationSlot()
            {
                Id = "16",
                User = seedUser
            };
            var seedRental = new Rental()
            {
                User = seedUser,
                Id = "5",
                Modified = DateTime.Now,
                Reservations = new List<ReservationSlot>() { seedReservation1, seedReservation2 }
            };
            ctx.Rentals.Add(seedRental); //exception thrown here for user already being tracked
            ctx.SaveChanges();
            ctx.Dispose();

Can EF core detect the same primary key and treat the entity as the same for tracking purposes?

Further technical details

EF Core version: 3.1.2
Database provider: npgsql
Target framework: .net core 3.1
Operating system: Win 10
IDE: vs 2019

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions