Add concurrency checks.

See also:
https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations
This commit is contained in:
Kevin Matsubara 2025-04-14 22:10:45 +02:00
parent f6d5733992
commit 2a09ddb0da
4 changed files with 37 additions and 1 deletions

View File

@ -22,6 +22,23 @@ context.Database.EnsureCreated();
// Rather than raise: "System.InvalidOperationException: Sequence contains no elements."
// var firstCoach = await context.Coaches.FirstOrDefaultAsync();
async Task ConcurrencyCheckExample()
{
var team = context.Teams.Find(1);
team.Name = "New name";
// To simulate a concurrency change, add a breakpoint and
// manually update the GUID in the database before SaveChangesAsync is called.
// See also: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=data-annotations
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine(ex.Message);
}
}
async Task TransactionWithSavepoint()
{

View File

@ -13,6 +13,12 @@ public class TeamConfiguration : IEntityTypeConfiguration<Team>
// example composite key configuration.
// builder.HasIndex(λ => new { λ.CoachId, λ.LeagueId }).IsUnique();
// SQL Server method to setup concurrency.
// builder.Property(λ => λ.Version).IsRowVersion();
// SQLite (and other) method to setup concurrency. See also BaseDomain model.
builder.Property(λ => λ.Version).IsConcurrencyToken();
builder.Property(λ => λ.Name).HasMaxLength(100).IsRequired();
builder.HasMany(m => m.HomeMatches) // A team has many home matches.

View File

@ -65,6 +65,9 @@ public class DeadBallZoneLeagueDbContext : DbContext
entry.Entity.CreatedDate = DateTime.UtcNow;
entry.Entity.CreatedBy = "Sample User";
}
// Update the concurrency token.
entry.Entity.Version = Guid.NewGuid();
}
return base.SaveChangesAsync(cancellationToken);
}

View File

@ -1,4 +1,6 @@
namespace EntityFrameworkCore.Domain;
using System.ComponentModel.DataAnnotations;
namespace EntityFrameworkCore.Domain;
// Since C#11, you do not need to wrap in accolades anymore.
public abstract class BaseDomainModel
{
@ -7,4 +9,12 @@ public abstract class BaseDomainModel
public DateTime ModifiedDate { get; set;}
public string? CreatedBy { get; set; }
public string? ModifiedBy { get; set; }
// SQL Server method to setup concurrency column.
// [Timestamp]
// public byte[] Version { get; set; }
// SQLite (and other) method to setup concurrency column.
[ConcurrencyCheck]
public Guid Version { get; set; }
}