falowil
falowil

Reputation: 124

Generate UnitTest that throws a DbUpdateConcurrencyException

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

Answers (1)

Alexander Petrov
Alexander Petrov

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

Related Questions