Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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