Changelog
The authoritative release history for all RCommon packages is maintained on GitHub Releases.
Full release notes: https://github.com/RCommon-Team/RCommon/releases
Recent Changes
3.1.2
Modular composition, bootstrapping ergonomics, opt-in CQRS transactions, and a docs consolidation pass covering navigation, eight new runnable examples, an end-to-end DDD recipe, and a machine-readable full API reference. All changes in this release are backward-compatible.
Added
- Modular composition support —
services.AddRCommon()can now be called from multiple modules in the same process. Registrations merge instead of duplicating or throwing on agreement. See Modular Composition. - New public API:
IRCommonBuilder.GetOrAddBuilder<TSubBuilder>(Func<TSubBuilder>)— third-partyWithX<T>extensions opt into sub-builder caching. - New public API:
IRCommonBuilder.GetBootstrapDiagnostics()— retrieve the soft-duplicate report stashed at host startup. - New public API:
IServiceCollection.IsRCommonInitialized()— true iffAddRCommon()has been called. - New internal
IHostedServiceruns the duplicate-descriptor scanner at host startup and emits a single warning on soft duplicates. ICqrsBuilder.AddUnitOfWorkToCommandBus()— opt-in unit-of-work wrapping for the nativeICommandBus, via a newUnitOfWorkCommandBusdecorator. Commands only, not queries; gives the native command bus parity with MediatR'sAddUnitOfWorkToRequestPipeline().TenantScope.Bypass()(RCommon.Security.Claims) — an ambient,AsyncLocal-based, nestable scope that suspends tenant-based repository filtering and stamping for its lifetime. A newTenantScopeAwareTenantIdAccessordecorator wrapsClaimsTenantIdAccessorand Finbuckle's tenant accessor automatically.- When exactly one data store is registered and
SetDefaultDataStorewas never called, it is now auto-inferred (logged atInformation); registering 2+ stores with no default now also logs informationally instead of only surfacing at first use. - Cross-builder startup diagnostics: a warning is logged when an in-memory event bus builder has subscriptions but no registered producer, and when the event router has events to dispatch but no producers are registered for them (both were previously silent).
- 8 new runnable
Examples/projects: Dapper, Linq2Db, Sagas, Blob Storage (Azure/S3), State Machines (Stateless), Multi-Tenancy (Finbuckle), and Event Handling (Outbox) — plus an end-to-end Domain-Driven Design recipe (Team/TeamMembershipaggregate). - A machine-readable full API reference, generated directly from built assemblies and their XML doc-comment sidecars.
Fixed
EFCoreAggregateRepository/EFCoreRepository.UpdateAsyncmisclassified newly-added child entities with non-default keys (e.g. sequential GUIDs) asModifiedinstead ofAdded, silently dropping the insert. Fixed with explicit reference-based change tracking.Include<TProperty>/ThenIncludethrew for collection navigations ("invalid inside an Include operation"); switched to EF Core's string-pathInclude()overload.AddSubscriber<TEvent, THandler>()onIInMemoryEventBusBuildersilently did nothing (the handler was never invoked) unless a separate, undocumented producer registration call was also made. The required producer is now auto-registered.- The zero-argument
RCommon.FluentValidation.ValidationBuilderExtensions.WithValidation<T>()overload — a byte-for-byte duplicate of theApplicationServicesoverload — was deleted, resolving an unconditionalCS0121ambiguity error when both packages were referenced together. - Dapper (
RCommon.Dapper):AddAsync/AddRangeAsyncdiscarded database-generated keys instead of writing them back to the entity after insert. Separately, Dommel's default key resolver treated anyGuid/string-typedIdproperty as database-generated, silently writingNULL/default into the primary key column. A newRCommonKeyPropertyResolverrestricts auto-generation guessing to numeric key types. - Linq2Db (
RCommon.Linq2Db): entities deriving fromBusinessEntity/AggregateRoot<TKey>failed every insert and query ("no such column") because Linq2Db does not honor[NotMapped]. A newIMetadataReaderimplementation excludes the framework-owned event-tracking properties from the entity's column mapping. RCommon.Finbuckle: an unusednew()generic constraint onFinbuckleMultiTenantBuilder<TTenantInfo>/FinbuckleTenantIdAccessor<TTenantInfo>broke compilation against Finbuckle's built-inTenantInfotype (which hasrequiredmembers) on net10.0. The constraint is now conditional per target framework, since net8.0/net9.0 reference an older Finbuckle version whose own interfaces still require it.
Changed
AddRCommon()is now idempotent: subsequent calls return the cachedIRCommonBuilder.- Singleton-style verbs (
WithSimpleGuidGenerator,WithSequentialGuidGenerator,WithDateTimeSystem,WithJsonSerialization<T>,WithSmtpEmailServices,WithSendGridEmailServices) are now idempotent on same-type re-registration. Different-type conflicts throwRCommonBuilderExceptionwith diagnostic messages. DataStoreFactoryOptions.Register<,>is now idempotent for identical(name, base, concrete)triples. Conflicts (same(name, base)with different concrete) throwUnsupportedDataStoreException(exception type preserved).AddProducer<T>now deduplicates by concrete producer type via descriptor scan — distinct producer types still coexist.- All sub-builder
WithX<T>extensions route throughGetOrAddBuilder<T>; generic constraints tightened towhere T : class, .... DataStoreNotFoundException's message now lists registered stores and remediation steps (message text only; exception type unchanged).EFCorePerisistenceBuilder(typo) renamed toEFCorePersistenceBuilder; the old name remains as an[Obsolete]forwarding subclass.- The
WithValidation<T>(Action<CqrsValidationOptions>)overload is marked[Obsolete]in favor of callingUseWithCqrs(...)insideWithValidation<T>(Action<T>).
Notes
- Strictly additive public API surface across this release. No method or interface signature removals, and no exception types changed on existing APIs.
- See
Examples/Bootstrapping/Examples.Bootstrapping.MultiModule/for a runnable demonstration of modular composition.
3.1.1
Bug-fix and security patch release.
Fixed
- Cross-host transactional outbox delivery. Added
OutboxOptions.ImmediateDispatch(defaulttrue, fully backward-compatible). When set tofalse, a unit of work persists outbox events but skips the best-effort post-commit in-process dispatch (Phase 3), leaving the durable poller (OutboxProcessingService) as the sole dispatcher and marker. This fixes silent loss of cross-host delivery, where a producing host marked rows processed before a subscriber on a different host could consume them. Producer-only hosts should setImmediateDispatch = false. See Outbox Producer/Processor Topology. - Outbox post-commit dispatch no longer marks a message processed when no in-process producer matched the event; it is left for the poller (defense-in-depth).
EFCoreOutboxStore.DeleteProcessedAsync/DeleteDeadLetteredAsyncused aDateTimeOffsetcomparison the SQLite provider could not translate. The comparison is now evaluated client-side; SQL Server / PostgreSQL behaviour is unchanged.
Security
- Pinned patched Microsoft.OpenApi 2.7.5 in
RCommon.Authorization.Web(net10 target), resolving CVE-2026-49451 (GHSA-v5pm-xwqc-g5wc), which was pulled transitively via Swashbuckle 10.x. - Upgraded the native SQLite binary (SQLitePCLRaw.lib.e_sqlite3 3.53.3) in the SQLite-backed test projects, resolving CVE-2025-6965 (GHSA-2m69-gcr7-jv3q).
Notes
- Strictly additive public API surface (
OutboxOptions.ImmediateDispatch); no signature removals, and no behavioural change at the default. - Website (Docusaurus) build-tooling npm advisories are tracked as a separate follow-up (Docusaurus 3.10 upgrade + theme swizzle migration).
Blob Storage Abstractions
Added RCommon.Blobs as a provider-agnostic blob storage abstraction layer, with two concrete implementations:
RCommon.Azure.Blobs— Azure Blob Storage implementationRCommon.Amazon.S3Objects— Amazon S3 implementation
Multi-Tenancy Support
Added complete multi-tenancy infrastructure:
RCommon.MultiTenancy— builder abstraction for registering tenancy providers viaWithMultiTenancy<T>()RCommon.Finbuckle— Finbuckle.MultiTenant integration, providingFinbuckleTenantIdAccessor<TTenantInfo>that bridges Finbuckle's resolved tenant context to theITenantIdAccessorconsumed by all repositories- All persistence providers (
EFCoreRepository,DapperRepository,Linq2DbRepository) automatically filter reads by tenant and stamp writes with the current tenant ID when entities implementIMultiTenant
Domain-Driven Design Support
Added soft delete and multitenancy to the entity layer:
ISoftDelete— opt-in interface for logical deletion; repositories setIsDeleted = trueand perform an UPDATE instead of a physical DELETEIMultiTenant— opt-in interface for tenant-scoped entities; repositories filter reads and stamp writes automaticallySoftDeleteHelperandMultiTenantHelperutility classes for filter expression building and entity stampingEntityNotFoundExceptionfor consistent "entity not found" error handling with type and ID context
Repository Soft Delete Extras
Extended all persistence providers with explicit soft delete and bulk operation support:
DeleteAsync(entity, isSoftDelete: bool)overload on all write repositories- Automatic
!IsDeletedfiltering on all read operations forISoftDeleteentities - Bulk delete via
ExecuteDeleteAsynconEFCoreRepository
Editorconfig and Code Hygiene
Added .editorconfig to enforce consistent code style across the solution. All public methods and complex code paths now carry XML documentation comments.
Versioning
RCommon uses MinVer for automatic semantic versioning from git tags. All packages in the solution share the same version number.
Version numbers follow Semantic Versioning:
- Major (
x.0.0) — breaking API changes - Minor (
0.x.0) — new features, backward-compatible additions - Patch (
0.0.x) — bug fixes and documentation updates
Pre-release versions (alpha, beta, rc) are tagged accordingly (e.g., v3.0.0-alpha.1).
Release Schedule
RCommon does not follow a fixed release cadence. Releases are made when meaningful features, bug fixes, or breaking changes are ready. Subscribe to GitHub Releases or watch the repository to be notified of new versions.
Filing Issues
Found a bug or missing a feature? Please open an issue on GitHub: