Replace multi-level relationship in Entity Framework Core 6 vs 7
Ask a question
I have the following model
public partial class Parent
{
public int IdParent { get; set; }
public virtual ICollection<FirstChild> FirstChild{ get; set; } = new List<FirstChild>();
}
public partial class FirstChild
{
public int IdFirstChild { get; set; }
public virtual ICollection<SecondChild> SecondChild { get; set; } = new List<SecondChild>();
}
public partial class SecondChild
{
public int IdSecondChild { get; set; }
public virtual ICollection<ThirdChild> ThirdChild{ get; set; } = new List<ThirdChild>();
}
public partial class ThirdChild
{
public int IdThirdChild { get; set; }
public String SomeProperty{ get; set; }
}
I update Parent with all it's relationships it this way :
var parentDB = _context.Parent.Single(x => x.IdParent == {id})
.Include(x => x.FirstChild)
.ThenInclude(x => x.SecondChild)
.ThenInclude(x => x.ThirdChild);
parentDb.FirstChild = {newCollectionValuesList}
_context.SaveChanges();
This works with EF Core 6 :
- If in
{newCollectionValuesList}, there is an element that already exists inparentDb.FirstChild(same ID), it get updated (with all its hierarchy) - If if does not exists, it will be added
- If it exists but was not specified in
{newCollectionValuesList}, it get deleted
When I upgraded to EF Core 7, I started to have this error :
The instance of entity type 'SecondChild' cannot be tracked because another instance with the key value '{IdSecondChild: XXXX}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached
I tried to add .AsNoTracking() in the query to resolve this, but my entities won't be updated
I don't want to loop manually through all the relationship and manually add the conditions to update the child elements
I didn't find anything related to that in What's new in EF7 nor in Breaking changes in EF7
This is a sample project with integration tests to reproduce the problem
Is this a bug ? because it works in EF6 Same result using dotnet6 and dotnet7
Note for triage: I am able to reproduce this. In 6.0, the existing instances are cascade-deleted before the new instances are tracked. In 7.0, the identity conflict happens before the cascade delete. Could be related to #30122.
FirstChild (Shared) {IdFirstChild: 11} Added
IdFirstChild: 11 PK
FirstChildName: 'firstChild1'
IdParent: 1 FK
IdParentNavigation: {IdParent: 1}
SecondChild: [{IdSecondChild: 111}, {IdSecondChild: 111}]
FirstChild (Shared) {IdFirstChild: 11} Deleted
IdFirstChild: 11 PK
FirstChildName: 'firstChild1'
IdParent: 1 FK
IdParentNavigation: <null>
SecondChild: [{IdSecondChild: 111}]
FirstChild {IdFirstChild: 12} Added
IdFirstChild: 12 PK
FirstChildName: 'firstChild2'
IdParent: 1 FK
IdParentNavigation: {IdParent: 1}
SecondChild: []
Parent {IdParent: 1} Unchanged
IdParent: 1 PK
ParentName: 'parent'
FirstChild: [{IdFirstChild: 11}, {IdFirstChild: 12}]
SecondChild (Shared) {IdSecondChild: 111} Added
IdSecondChild: 111 PK
IdFirstChild: 11 FK
SecondChildName: 'secondChild1'
IdFirstChildNavigation: {IdFirstChild: 11}
ThirdChild: [{IdThirdChild: 1111}, {IdThirdChild: 1111}]
SecondChild (Shared) {IdSecondChild: 111} Deleted
IdSecondChild: 111 PK
IdFirstChild: 11 FK
SecondChildName: 'secondChild1'
IdFirstChildNavigation: {IdFirstChild: 11}
ThirdChild: [{IdThirdChild: 1111}]
ThirdChild (Shared) {IdThirdChild: 1111} Added
IdThirdChild: 1111 PK
IdSecondChild: 111 FK
ThirdChildName: 'thirdChild1'
IdSecondChildNavigation: {IdSecondChild: 111}
ThirdChild (Shared) {IdThirdChild: 1111} Deleted
IdThirdChild: 1111 PK
IdSecondChild: 111 FK
ThirdChildName: 'thirdChild1'
IdSecondChildNavigation: {IdSecondChild: 111}
@iheb719 Workaround is to for the deletion before replacing the collection:
public void UpdateParentChild(Parent parentUpdate)
{
var existingParent = _context.Parent.Local.FirstOrDefault(x => x.IdParent == parentUpdate.IdParent);
existingParent?.FirstChild.Clear();
_context.ChangeTracker.DetectChanges();
var parentBd = _context.Parent
.Include(x => x.FirstChild)
.ThenInclude(x => x.SecondChild)
.ThenInclude(x => x.ThirdChild)
.Single(x => x.IdParent == parentUpdate.IdParent);
parentBd.ParentName = parentUpdate.ParentName;
parentBd.FirstChild = parentUpdate.FirstChild;
_context.SaveChanges();
}
Thanks @ajcvickers the workaround is working