Change tracking in Entity Framework Core 2.0 changed my code behavior

c# entity-framework-core


That code worked fine in Entity Framework 1.0 could also be 1.1.

var pupilFound = await context.Pupils.SingleOrDefaultAsync(p => p.Id == pupil.Id);
if (pupilFound == null)
    throw new BadDataException($"{nameof(pupil.Id)} is not valid");
if (pupilFound.UserId != userId)
    throw new NotAuthorizedException();

pupil.UserId = userId;
context.Entry(pupil).State = EntityState.Modified;
await contextSaveChangesAsync();

Now with Entity Framework Core 2.0 I got that exception with error:

Cannot be tracked because another instance of this type with the same key is already being tracked

A quick fix would be:

var pupilFound = await context.Pupils.AsNoTracking.SingleOrDefaultAsync(p => p.Id == pupil.Id);

That way the tracking is disabled, but that defeats the purpose of the AsNoTracking method regarding the official recommendation:

Disabling change tracking is useful for read-only scenarios because it avoids
        //     the overhead of setting up change tracking for each entity instance. You should
        //     not disable change tracking if you want to manipulate entity instances and persist
        //     those changes to the database using Microsoft.EntityFrameworkCore.DbContext.SaveChanges

How else would update the pupil with all its properties coming from the client?

1/6/2018 1:49:33 PM

Popular Answer

As said in the comment, it's highly unlikely that this code could run in the previous "beta" versions of EF-core (i.e. < v. 2). They were buggy, but not that bad.

Anyway, you're doing a lot there that could be less ceremonial.

First, anything related to authorization should be dealt with earlier in the processing pipeline by applying filters, so you won't have to write this repetitive code time and again. But that's off-topic here. (Assuming that you're working in ASP.Net).

Secondly, on-topic, you really don't need to check whether the id exists in the database. Just attach the pupil object as modified and save changes. You have to catch exceptions anyway, so why not catch DbUpdateExceptions specifically, which will be thrown when the pupil has been deleted in the mean time (and other possible reasons).

1/6/2018 3:21:02 PM

Related Questions


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow