Compile-time optimistic concurrency for EF Core. Stamp [Optimistic] on any partial entity class and the source generator wires up a RowVersion byte array property and the IOptimisticEntity interface. Two ready-made retry extensions handle concurrent-write conflicts without any Polly dependency.
dotnet add package Swevo.EFCore.RowVersionusing EFCore.RowVersion;
[Optimistic]
public partial class Order
{
public int Id { get; set; }
public string Description { get; set; } = "";
// byte[] RowVersion { get; set; } is generated automatically
}The class must be
partial. Non-partial classes produce diagnosticRVRS001.
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
=> modelBuilder.AddOptimisticConcurrencyConfiguration();
}// Client wins — re-applies your changes over the updated DB values
await db.SaveChangesClientWinsAsync(maxRetries: 3);
// Database wins — discards your in-memory changes, accepts the DB values
await db.SaveChangesDatabaseWinsAsync(maxRetries: 3);On SQL Server the generated [Timestamp] attribute on RowVersion is recognised by EF Core convention and automatically configures the property as a database-managed rowversion column. No additional code is needed.
// For SQL Server, also tell EF Core to use the database-managed rowversion:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.AddOptimisticConcurrencyConfiguration();
// Optional – [Timestamp] convention already does this for SQL Server:
// modelBuilder.Entity<Order>().Property(o => o.RowVersion).IsRowVersion();
}For SQLite, PostgreSQL, and other providers, override ValueGeneratedNever() and update the version manually on each save (e.g. using an interceptor):
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.AddOptimisticConcurrencyConfiguration();
modelBuilder.Entity<Order>().Property(o => o.RowVersion).ValueGeneratedNever();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.AddInterceptors(new VersionIncrementInterceptor());
// Interceptor that stamps a new Guid on every save
sealed class VersionIncrementInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(DbContextEventData e, InterceptionResult<int> r)
{
foreach (var entry in e.Context!.ChangeTracker.Entries<IOptimisticEntity>()
.Where(x => x.State is EntityState.Added or EntityState.Modified))
entry.Entity.RowVersion = Guid.NewGuid().ToByteArray();
return base.SavingChanges(e, r);
}
}| Piece | What it does |
|---|---|
[Optimistic] |
Marks the entity for code generation |
| Source generator | Emits [Timestamp] byte[] RowVersion + IOptimisticEntity implementation |
AddOptimisticConcurrencyConfiguration |
Calls IsConcurrencyToken() for all IOptimisticEntity entities in OnModelCreating |
SaveChangesClientWinsAsync |
Retries on DbUpdateConcurrencyException; refreshes original values from DB, re-applies client values |
SaveChangesDatabaseWinsAsync |
Retries on DbUpdateConcurrencyException; reloads the entity, discards client changes |
[Optimistic] // → RowVersion + optimistic concurrency retry
[Auditable] // → CreatedAt, UpdatedAt
[SoftDelete] // → IsDeleted + global query filter
[Tenant] // → TenantId + per-tenant query filter
public partial class Order
{
public OrderId Id { get; set; }
}- .NET 8+
- EF Core 8+
🌐 Full suite overview: swevo.github.io
| Package | Description |
|---|---|
| AutoLog.Generator | Compile-time high-performance logging — [Log(Level, Message)] generates LoggerMessage.Define. AOT-safe. |
| AutoHttpClient.Generator | Compile-time typed HTTP client — [HttpClient] on an interface generates a strongly-typed client. AOT-safe Refit alternative. |
| AutoDispatch.Generator | Compile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. No MediatR, no reflection. |
| AutoWire | Compile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection registration code. |
| AutoMap.Generator | Compile-time object mapping with generated extension methods. AOT-safe AutoMapper alternative. |
| Package | Downloads | Description |
|---|---|---|
| Swevo.EFCore.Outbox | Transactional outbox pattern for EF Core + AutoBus | |
| Swevo.EFCore.StronglyTyped | Compile-time strongly-typed ID generation for | |
| Swevo.EFCore.SoftDelete | Compile-time soft-delete generation for EF Core entities using Roslyn source generators | |
| Swevo.EFCore.Seeding | Fluent, idempotent, dependency-ordered seed data for EF Core | |
| Swevo.EFCore.Pagination | Offset and cursor-based pagination for EF Core | |
| Swevo.EFCore.JsonColumn | Compile-time JSON column configuration for EF Core 8+ — [JsonColumn] on owned navigation properties generates ConfigureJsonColumns(ModelBuilder) with OwnsOne( | |
| Swevo.EFCore.BulkOperations | Free, MIT-licensed bulk insert/update/delete for EF Core | |
| Swevo.EFCore.MultiTenant | Compile-time multi-tenancy for EF Core |
MIT