0

Sorry to raise this one again - I see it plenty of times on here but I am still puzzled about it in my case, especially because I seem to be able to 'overcome' it, but I'm not sure I like how, and I would love to understand why.

I have a many to many relationship between Staff and Departments, with the StaffDepartment table being set up with a composite key like so:

modelBuilder.Entity<StaffDepartment>() .HasKey(sd => new { sd.StaffId, sd.DepartmentId });

I suspect that must be something to do with the exception I sometimes get:

InvalidOperationException: The instance of entity type 'StaffDepartment' cannot be tracked because another instance with the same key value for {'StaffId', 'DepartmentId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

I say sometimes because, strangely, if I change the department in my staff details page and update, it goes through fine, but when I leave it the same (maybe just changing the staff name for example) I receive this exception.

I tried implementing the various suggestions on this similar but apparently different SO discussion and all sorts of ways of tinkering with my repository update method, and strangely enough this is the only way I have got it working:

public async Task UpdateStaff(StaffDto staff)
        {
            var staffToUpdate = await _db.Staff
                .Include(d => d.Departments)
                .ThenInclude(d => d.Department)
                .SingleOrDefaultAsync(s => s.Id == staff.Id);

            // Without either these two line I get the exception
              _db.StaffDepartment.RemoveRange(staffToUpdate.Departments);
              await _db.SaveChangesAsync();               

            _mapper.Map(staff, staffToUpdate);
            await _db.SaveChangesAsync();
        }

So it seems strange that I should have to call SaveChangesAsync() twice in same method. What is actually happening there? And how would I achieve the suggestion in the error When attaching existing entities, ensure that only one entity instance with a given key value is attached without doing so.

Entity classes look like so:

public class Staff
    {
        public int Id { get; set; }
        ...
        public ICollection<StaffDepartment> Departments { get; set; }
    }

public class Department
    {
        public int DepartmentId { get; set; }
        ...
        public ICollection<StaffDepartment> Staff { get; set; }
    }

public class StaffDepartment
    {
        public int StaffId { get; set; }
        public Staff Staff { get; set; }
        public int DepartmentId { get; set; }
        public Department Department { get; set; }

    }

And the staffDto that comes back from the domain etc. is simply:

public class StaffDto
    {
        public int Id { get; set; }
        ...
        public List<DepartmentDto> Departments { get; set; }
    }

The repositories are all registered as transient i.e.

services.AddTransient<ICPDRepository, CPDRepository>();
Mikustykus
  • 300
  • 7
  • 16
  • What is happening here is that `_mapper.Map` cannot be used for applying disconnected entity graph changes. It needs more intelligent processing - like [Detached.EntityFramework](https://learn.microsoft.com/en-us/ef/core/extensions/#detachedentityframework) or [AutoMapper.Collection](https://github.com/AutoMapper/AutoMapper.Collection) – Ivan Stoev Apr 06 '18 at 09:17
  • @IvanStoev Ah, thanks, I shall have to look into that. But how am I ending up with "another instance with the same key value for {'StaffId', 'DepartmentId'}"? And why does it work if the department is changed? – Mikustykus Apr 06 '18 at 11:34
  • The thing is, when you call `_db.StaffDepartment.RemoveRange(staffToUpdate.Departments)`, the entity **instances** you've removed are still tracked by the context (with state `Deleted`). So anytime you try to add new entity **instance** of the same type and with the same PK, you get such exception. After calling `SaveChanges`, the `Deleted` instances are removed (no more tracked). The problem with AM `Map` is that it creates **new instances** with the same PK. This is what AM.Collection is trying to address. – Ivan Stoev Apr 06 '18 at 11:43
  • I see. Thing is, I only added `_db.StaffDepartment.RemoveRange(staffToUpdate.Departments)` in one of my many attempts get over the exception. Couldn't I avoid this step entirely if the departments remained the same? Or is it the general practice to remove the many-to-many collection and add afresh, be it the same or otherwise? – Mikustykus Apr 06 '18 at 11:59
  • The best approach is to detect the added/deleted entries. – Ivan Stoev Apr 06 '18 at 12:20

0 Answers0