ResQ.BuildingBlocks.Adapters.Persistence
0.2.0
dotnet add package ResQ.BuildingBlocks.Adapters.Persistence --version 0.2.0
NuGet\Install-Package ResQ.BuildingBlocks.Adapters.Persistence -Version 0.2.0
<PackageReference Include="ResQ.BuildingBlocks.Adapters.Persistence" Version="0.2.0" />
<PackageVersion Include="ResQ.BuildingBlocks.Adapters.Persistence" Version="0.2.0" />
<PackageReference Include="ResQ.BuildingBlocks.Adapters.Persistence" />
paket add ResQ.BuildingBlocks.Adapters.Persistence --version 0.2.0
#r "nuget: ResQ.BuildingBlocks.Adapters.Persistence, 0.2.0"
#:package ResQ.BuildingBlocks.Adapters.Persistence@0.2.0
#addin nuget:?package=ResQ.BuildingBlocks.Adapters.Persistence&version=0.2.0
#tool nuget:?package=ResQ.BuildingBlocks.Adapters.Persistence&version=0.2.0
ResQ.BuildingBlocks.Adapters.Persistence
The EF Core 9 persistence adapter (driven ring). Provider-agnostic — it references only EF Core and its Relational package, so the consumer picks the provider (Npgsql, SQLite, …) in the configure callback. Depends only on ResQ.BuildingBlocks.Application + ResQ.BuildingBlocks.Domain — never on the messaging adapter (the outbox/inbox use the messaging ports that live in the application core).
The keystone: EfUnitOfWork
EfUnitOfWork is the scoped IUnitOfWork. Its SaveChangesAsync dispatches domain events before the underlying DbContext.SaveChanges, in a drain loop over ChangeTracker.Entries<IHasDomainEvents>():
- Collect tracked aggregates with pending events, snapshot and clear them.
await IDomainEventDispatcher.DispatchAsync(events)— a handler may enqueue an outbox row or raise more events.- Repeat until no aggregate has pending events, then call the underlying
SaveChangesAsync.
Because dispatch happens inside the same save, outbox rows a handler enqueues commit atomically with the aggregate write. There is deliberately no domain-event-dispatch interceptor: EF's internal service provider does not contain application services. Contract: commits must flow through IUnitOfWork.SaveChangesAsync for domain events to dispatch.
What's in the box
Repository<TAggregate,TId>/ReadRepository<TAggregate,TId>— specification-driven aggregate repositories; the read side forcesAsNoTracking.SpecificationEvaluator— bridges the EF-freeISpecification<T>(criteria, includes, ordering, paging, tracking, split-query, ignore-filters) onto anIQueryable<T>.TransactionBehavior<TRequest,TResponse>— wraps commands (detected through the CQRS marker interfaces, soICommand<T>is caught too) in a transaction via EF's execution strategy. Queries pass through.AuditInterceptor+IAuditable— a singleton save-changes interceptor that stamps created/modified timestamps fromIClock.- Transactional outbox —
OutboxMessage(bytes payload),OutboxMessageConfiguration,IOutbox/EfOutbox(enqueue in the same transaction),OutboxRelay(at-least-once background publisher),OutboxOptions. - Inbox idempotency —
InboxMessage,InboxMessageConfiguration,EfIdempotencyStore(dedupe by(messageId, handler)). ModelBuilderExtensions—ApplyResqInfrastructure()maps the outbox/inbox tables;ApplyConfigurationsFrom(assembly)applies your own configs.
Wiring
services.AddResqPersistence<AppDbContext>(
(sp, options) => options.UseNpgsql(connectionString),
setup =>
{
setup.UseOutbox = true; // register EfOutbox + OutboxRelay
setup.UseTransactionBehavior = true; // register TransactionBehavior last (innermost)
setup.EnableAudit = true; // attach AuditInterceptor
});
services.AddResqRepositories(typeof(AppDbContext).Assembly);
AddResqPersistence pools the context (AddDbContextPool), relays a scoped DbContext, and registers EfUnitOfWork/EfIdempotencyStore scoped and AuditInterceptor singleton. In OnModelCreating, call modelBuilder.ApplyResqInfrastructure().ApplyConfigurationsFrom(GetType().Assembly).
Apache-2.0 · part of resq-software/dotnet.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- FluentValidation (>= 11.11.0)
- FluentValidation.DependencyInjectionExtensions (>= 11.11.0)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- ResQ.BuildingBlocks.Application (>= 0.2.0)
- ResQ.BuildingBlocks.Domain (>= 0.2.0)
- Scrutor (>= 5.0.1)
- System.Text.Json (>= 9.0.19)
-
net9.0
- FluentValidation (>= 11.11.0)
- FluentValidation.DependencyInjectionExtensions (>= 11.11.0)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- ResQ.BuildingBlocks.Application (>= 0.2.0)
- ResQ.BuildingBlocks.Domain (>= 0.2.0)
- Scrutor (>= 5.0.1)
- System.Text.Json (>= 9.0.19)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.2.0 | 325 | 8/16/2026 |