5

I am a newbie to EF and am struggling to do what seems like a basic update. I have read numerous posts on similar issues. From what I gather this is not currently easy to do in EF Core 2.1 and requires a complex workaround. Is this actually possible or should I simply update the child entities directly and individually?

Note: if I just update the exampleA and do not include the child entities it works fine.

I get the following error:

An unhandled exception occurred while processing the request. InvalidOperationException: The instance of entity type 'ExampleB' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values. Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap.ThrowIdentityConflict(InternalEntityEntry entry)

Stack Query Cookies Headers InvalidOperationException: The instance of entity type 'ExampleB' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

I have created a basic example of the issue. First stackoverflow post also so let me know if more info is required.

Any help would be greatly appreciated.

WebAPI controller:

[HttpPut]
    public async Task<ActionResult> UpdateExample(ExampleADto exampleADto)
    {
        var exampleAFromDB = await _context.ExampleA.Include(x => x.ExampleBs).Where(x => x.Id == exampleADto.Id).SingleOrDefaultAsync();

        if (exampleAFromDB == null)
            return NotFound();

        _mapper.Map(exampleADto, exampleAFromDB);

        if (await _context.SaveChangesAsync() > 0)
            return Ok(exampleAFromDB);

        throw new Exception("Failed to update ExampleA"); 
    }

exampleA in DB before update:

{
    "id": 1,
    "description": "Example A1",
    "exampleBDtos": [
        {
            "id": 1,
            "description": "B1",
            "someNumber": 1
        },
        {
            "id": 2,
            "description": "B2",
            "someNumber": 2
        },
        {
            "id": 3,
            "description": "B3",
            "someNumber": 3
        }
    ]
}

exampleADto:

{
    "id": 1,
    "description": "Example A1 Updated",
    "exampleBDtos": [
        {
            "id": 1,
            "description": "B1 Updated",
            "someNumber": 1
        },
        {
            "id": 2,
            "description": "B2",
            "someNumber": 2
        },
        {
            "id": 3,
            "description": "B3",
            "someNumber": 3
        }
    ]
}

exampleA after Automapping:

{
"id": 1,
"description": "Example A1 Updated",
"exampleBDtos": [
    {
        "id": 1,
        "description": "B1 Updated",
        "someNumber": 1
    },
    {
        "id": 2,
        "description": "B2",
        "someNumber": 2
    },
    {
        "id": 3,
        "description": "B3",
        "someNumber": 3
    }
]

}

exampleA in DB after failed update (no change):

{
    "id": 1,
    "description": "Example A1",
    "exampleBDtos": [
        {
            "id": 1,
            "description": "B1",
            "someNumber": 1
        },
        {
            "id": 2,
            "description": "B2",
            "someNumber": 2
        },
        {
            "id": 3,
            "description": "B3",
            "someNumber": 3
        }
    ]
}

AutoMapper Profiles:

public AutoMapperProfiles()
    {
        CreateMap<ExampleA, ExampleADto>()
            .ForMember(x => x.ExampleBDtos, opt => opt.MapFrom(x => x.ExampleBs));
        CreateMap<ExampleB, ExampleBDto>();

        CreateMap<ExampleADto, ExampleA>()
            .ForMember(x => x.ExampleBs, opt => opt.MapFrom(x => x.ExampleBDtos));
        CreateMap<ExampleBDto, ExampleB>();
    }

Update 1: Added object after automapping

Update 2: It seems Automapper does not map collections without dropping and recreating the items which messes with EF.

AutoMapper.Collection https://www.nuget.org/packages/AutoMapper.Collection/ does not currently work with EF Core so I have simply created a function myself to manually map and update the collection.

MadMac
  • 4,048
  • 6
  • 32
  • 69
  • 1
    Check AutoMapper.Collection. – Lucian Bargaoanu Aug 24 '18 at 00:56
  • The AutoMapping seems to work fine. I have added the object in the post after AutoMapper maps it. – MadMac Aug 24 '18 at 03:23
  • Plese, add to your query, AsNoTracking(), maybe that helps you. By the way avoid using VAR and try it in a strongly typed manner. – Sergio Rezende Aug 24 '18 at 03:29
  • 1
    Shortly - you should handle that yourself. Auto-mapping only *seems* to work because it replaces the collection items with new instances, which in turn causes the EF Core exception in question. As Lucian pointed out, AutoMapper.Collection could help with add/update of the child entities. But not with delete. You could also try [Detached.EntityFramework](https://learn.microsoft.com/en-us/ef/core/extensions/#detachedentityframework) but I haven't tried it and can't share opinion. – Ivan Stoev Aug 24 '18 at 14:51
  • Great thanks for the explanation @IvanStoev. I have confirmed automapper is the issue by manually updating the entities within the collection and this works. I will take a look at using AutoMapper.Collection. Any idea on whether there are plans to fix this in EF Core / Automapper or is AutoMapper.Collections always the way to go? – MadMac Aug 26 '18 at 22:15
  • It appears AutoMapper.Collection does not currently work with EF Core so I have simply created a function myself to manually map and update the collection. Thanks for the nudge in the right direction. – MadMac Aug 28 '18 at 22:30
  • @MadMac can you share your manual function? – sajjad Sep 28 '19 at 06:57

0 Answers0