Reputation: 124
I'm trying to create a test in .Net 7EFCore to generate a DbUpdateConcurrencyException but not succeding in doing that. Basically trying to modify it using different context instances. Apparantly there is something I have misunderstood, why does this not throw an exception when changes have been made in a second context and I then try to modify and save it with the first again? What is the correct way of writing a test for this? The goal is to be able to test implementation of handling of concurrency issues so just throwing the exception is not enough, I want to get it into a state where there are changes and I get an error message like Modified Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s); data may have been modified or deleted since entities were loaded.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Xunit;
namespace UnitTests;
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
public virtual DbSet<MyObject> MyObjects { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyObject>(entity => entity.ToTable("MyObject"));
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
try
{
return await base.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException ex)
{
// Implement handling
}
}
}
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ExceptionTest
{
[Fact]
public async Task DbUpdateConcurrencyExceptionTest()
{
// Arrange
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "SaveChangesAsyncTest")
.Options;
await using var context = new MyContext(options);
var myObject = new MyObject { Id = 1, Name = "name when added" };
context.MyObjects.Add(myObject);
var result = await context.SaveChangesAsync();
// Modify the object using a new context instance
await using var context2 = new MyContext(options);
var myObject2 = await context2.MyObjects.FindAsync(myObject.Id);
myObject2.Name = "New name";
await context2.SaveChangesAsync();
// Modify the original object using the original context instance
myObject.Name = "Renamed again";
await context.SaveChangesAsync();
// Modify the same entity from the second context instance
myObject2.Name = "Changed by second context";
await context2.SaveChangesAsync();
// Try to save the changes to the first context instance, which should result in a DbUpdateConcurrencyException
myObject.Name = "Changed by first context";
await Assert.ThrowsAsync<DbUpdateConcurrencyException>(() => context.SaveChangesAsync());
}
}
Upvotes: 0
Views: 354
Reputation: 14231
The next piece of code will throw a DbUpdateConcurrencyException.
using var db = new MyContext();
var obj = db.MyObjects.First();
obj.Name = "new value";
using var db2 = new MyContext();
var obj2 = db2.MyObjects.First();
db2.MyObjects.Remove(obj2);
db2.SaveChanges();
db.SaveChanges();
In real code, this can happen when one client deletes an entity that another client is currently working with.
Upvotes: 2