-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
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