I'm working with Entity Framework Core and encountering the following error:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException
Attempted to update or delete an entity that does not exist in the store.
at Microsoft.EntityFrameworkCore.InMemory.Storage.Internal.InMemoryTable`1.Update(IUpdateEntry entry, IDiagnosticsLogger`1 updateLogger)
at Microsoft.EntityFrameworkCore.InMemory.Storage.Internal.InMemoryStore.ExecuteTransaction(IList`1 entries, IDiagnosticsLogger`1 updateLogger)
at Microsoft.EntityFrameworkCore.InMemory.Storage.Internal.InMemoryDatabase.SaveChangesAsync(IList`1 entries, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Personnel.Infrastructure.Repositories.Persons.PersonWriteRepository.UpdateAsync(Person person, CancellationToken cancellationToken) in C:\Users\user\RiderProjects\staffpro.learn.redkozubov\src\Services\Personnel\Infrastructure\Personnel.Infrastructure.Repositories\Persons\PersonWriteRepository.cs:line 54
at Personnel.Unit.RepositoryTests.Persons.Write.PersonWriteRepositoryPositiveTests.UpdateAsync_WithWorkExperience_AddsPersonAndContext(Person person, WorkExperienceDto workExperienceDto, PersonWriteRepository writeRepository, PersonReadRepository readRepository) in C:\Users\user\RiderProjects\staffpro.learn.redkozubov\src\Services\Personnel\Tests\Personnel.Unit\RepositoryTests\Persons\Write\PersonWriteRepositoryPositiveTests.cs:line 71
at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_0.<<InvokeTestMethodAsync>b__1>d.MoveNext() in /_/src/xunit.execution/Sdk/Frameworks/Runners/TestInvoker.cs:line 276
--- End of stack trace from previous location ---
at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in /_/src/xunit.execution/Sdk/Frameworks/ExecutionTimer.cs:line 48
at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in /_/src/xunit.core/Sdk/ExceptionAggregator.cs:line 90
This error occurs when I try to save changes to an entity with
await _personnelContext.SaveChangesAsync(cancellationToken)
after attempting to update a Person
entity with associated WorkExperience
.
Here's the code:
[Theory]
[MemberData(nameof(ValidTestDataGenerator.GetPersonWithWorkExperienceDataAndRepositories), MemberType = typeof(ValidTestDataGenerator))]
internal async Task UpdateAsync_WithWorkExperience_AddsPersonAndContext(Person person, WorkExperienceDto workExperienceDto, PersonWriteRepository writeRepository, PersonReadRepository readRepository)
{
// Arrange
await writeRepository.AddAsync(person);
var dbPerson = await readRepository.GetByIdAsync(person.Id);
dbPerson.AddWorkExperience(
workExperienceDto.Id,
workExperienceDto.JobTitle,
workExperienceDto.Street,
workExperienceDto.Country,
workExperienceDto.City,
workExperienceDto.Description,
workExperienceDto.HiringDate,
workExperienceDto.OrganizationName,
workExperienceDto.DismissalDate
);
// Act
await writeRepository.UpdateAsync(dbPerson);
// Assert
dbPerson.Should().BeEquivalentTo(person);
}
Repositories:
public class PersonReadRepository
{
private readonly DbSet<Person> _persons;
public PersonReadRepository(PersonnelContext personnelContext)
{
_persons = personnelContext.Set<Person>();
}
public async Task<Person?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
return await _persons.FindAsync(id, cancellationToken);
}
}
public class PersonWriteRepository
{
private readonly DbSet<Person> _persons;
private readonly PersonnelContext _personnelContext;
public PersonWriteRepository(PersonnelContext personnelContext, IPersonReadRepository personReadRepository)
{
_personnelContext = personnelContext;
PersonReadRepository = personReadRepository;
_persons = personnelContext.Set<Person>();
}
public IPersonReadRepository PersonReadRepository { get; }
public async Task AddAsync(Person person, CancellationToken cancellationToken = default)
{
await _persons.AddAsync(person, cancellationToken);
await _personnelContext.SaveChangesAsync(cancellationToken);
}
public async Task UpdateAsync(Person person, CancellationToken cancellationToken = default)
{
_persons.Update(person);
await _personnelContext.SaveChangesAsync(cancellationToken);
}
}
What could be causing the DbUpdateConcurrencyException
error in this case, and how can I fix it? Should I implement optimistic concurrency control, or is there something wrong with how I'm handling entity updates or state tracking?
The error DbUpdateConcurrencyException
happens when I call
await _personnelContext.SaveChangesAsync(cancellationToken);
inside the UpdateAsync
method of the PersonWriteRepository
.
I add a Person
to the database via AddAsync
. Then I update the Person
by adding some WorkExperience
data to navigation property in the Person's ICollection<WorkExperience> WorkExperiences
.
The update fails with the error mentioned above.
I checked whether the entity is in the correct state before updating, but still get the error.
The error Attempted to update or delete an entity that does not exist in the store
is saying WorkExperience
doesn't exist in the database since on update EF Core tries to find the primary key of the navigation but seems in your code you added a child (WorkExperience) to the Person with a arbitrary Id.
So there are two options to fix:
1- Inserting Person and with its children in one go to the database:
// Arrange
person.AddWorkExperience(
workExperienceDto.Id,
workExperienceDto.JobTitle,
workExperienceDto.Street,
workExperienceDto.Country,
workExperienceDto.City,
workExperienceDto.Description,
workExperienceDto.HiringDate,
workExperienceDto.OrganizationName,
workExperienceDto.DismissalDate
);
// add Person and its children
await writeRepository.AddAsync(person);
var dbPerson = await readRepository.GetByIdAsync(person.Id);
// rest of the code
2- First add the WorkExperience and then use the primary key
The point is you can't expect EF Core to insert the children using the Update method.