Efcore: Entity already tracked exception for entity with multiple references to same navigation property

Created on 1 Mar 2020  路  4Comments  路  Source: dotnet/efcore


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

``` C#
class Program
{
static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var ctx = host.Services.GetRequiredService();
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:

``` C#
           //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

closed-question customer-reported

All 4 comments

Don't know if somehow this is also related to this issue:
https://github.com/dotnet/efcore/issues/19984
When we have two entities with the same Id in an object graph that we want to attach do EFCore.
In your case the User entity (in my case Author entity).

Don't know if somehow this is also related to this issue:

19984

When we have two entities with the same Id in an object graph that we want to attach do EFCore.
In your case the User entity (in my case Author entity).

Sounds like the same issue. I can provide both projects, one .net core 2.0 and one .net core 3.0 that produces this issue if needed. I think EF Core should be able to detect that the same primary key should map to the same entity.

@vflame

is it possible to context.Attach or context.Update disconnected entities with multiple references to a same entity? Why or why not?

This is not supported because in general resolution of conflicts between two or more different instances in the same graph is very ambiguous. Property values may be different, but more importantly, for anything other than a single entity with no relationships, the connections in the graph are also different.

See also #20124

Was this page helpful?
0 / 5 - 0 ratings