Eternet.Crud.Relational
3.1.22
Prefix Reserved
dotnet add package Eternet.Crud.Relational --version 3.1.22
NuGet\Install-Package Eternet.Crud.Relational -Version 3.1.22
<PackageReference Include="Eternet.Crud.Relational" Version="3.1.22" />
<PackageVersion Include="Eternet.Crud.Relational" Version="3.1.22" />
<PackageReference Include="Eternet.Crud.Relational" />
paket add Eternet.Crud.Relational --version 3.1.22
#r "nuget: Eternet.Crud.Relational, 3.1.22"
#:package Eternet.Crud.Relational@3.1.22
#addin nuget:?package=Eternet.Crud.Relational&version=3.1.22
#tool nuget:?package=Eternet.Crud.Relational&version=3.1.22
Eternet.Crud.Relational
Relational CRUD runtime, generated relational bridge conventions, bulk primitives, and relational outbox support for
Eternet.Mediator.
Installation
dotnet add package Eternet.Crud.Relational
Current Package Shape
As of April 2026, the supported authoring model is:
- request-first relational CRUD, where the public contract stays on the contracts/public handler and the internal relational request becomes the semantic source of truth
- aggregate-root semantics for single-entity create, update, delete, and upsert lanes
- first-class homogeneous bulk create, bulk delete, and bulk update lanes
- generated relational bridges that bind deterministic request/runtime inputs and publish reserved downstream outputs
- explicit
IDomainEventplus relational outbox projection as the default outbox model - legacy
CreateEntityCommand<Response>/UpdateEntityCommand<Response>/DeleteEntityCommand<Response>and[HandlerForEntity<TEntity>]only as compatibility paths
The modern relational lane is centered on a single explicit relational request type:
public sealed partial class UpdateJournalEntryHandler :
UpdateJournalEntry,
IRelationalUpdateImplementation<UpdateJournalEntryRelational.Request>
{
public override Response Handle(Request request) => request.StepsResults!.Response!;
}
public static partial class UpdateJournalEntryRelational
{
public sealed partial record Request
: RelationalUpdateEntityCommand<AccountingContext, JournalEntry, int>
{
public override int GetKey() => Id;
}
}
For update work, the internal relational request above is the supported replacement for authoring new features on
UpdateEntityCommand<Response>.
Recent Changes
The last two weeks materially changed both Eternet.Crud.Relational and Eternet.Crud.Relational.Generator:
- bulk delete, bulk create, and bulk update are now first-class relational lanes instead of bespoke custom-write patterns
- relational CRUD moved to the request-first API where
IRelational*Implementation<TRelationalRequest>declares one explicit internal relational request type - create/update/delete/upsert semantics are documented and enforced at the aggregate-root boundary
- generated bridge steps now return
StepResult<T>and preserve compatible custom relational result types for single-entity lanes GeneratedPipelineRuntimeis no longer the public runtime anchor for generated bridge glue; generated code now stays behind assembly-local helpers backed byGeneratedBindingRuntime- relational outbox authoring now defaults to explicit
IDomainEventemission plus registered projector translation; the old legacy outbox compatibility APIs were removed - generated bridge policy is emitted as assembly metadata so test/runtime discovery can reason about handlers across files and referenced assemblies
- nested relational handlers now get deterministic unique bridge artifact names
[RelationalEntityBinding("Alias")]can publish an additional downstream alias for the aggregate entity while keeping the defaultEntityoutput- scalar binding in generated bridges was tightened so request members bind deterministically by member name and type
Preferred Authoring Model
For new relational features:
- Keep the public request/contract in the contracts or public server layer and express API semantics with
IEndpointCreate,IEndpointUpdate,IEndpointDelete,IEndpointUpsert, or the relevant query contract. - Keep the public handler on that contract and implement one of the
IRelationalCreate/Update/Delete/UpsertImplementation<TRelationalRequest>interfaces. - Declare an app-owned internal relational request that inherits the appropriate relational request base:
RelationalCreateEntityCommand<...>,RelationalUpdateEntityCommand<...>,RelationalDeleteEntityCommand<...>, orRelationalUpsertEntityCommand<...>. - Let
Eternet.Crud.Relational.Generatoraugment that request, generate the bridge, and publish the reserved downstream outputs. - Keep domain materialization/mutation and
IDomainEventemission in the app-owned relational business handler or pipeline steps.
The preferred bridge ownership split is:
- public handler: API contract shape, authorization/binding concerns when needed, and final response assembly from
request.StepsResults - generated bridge: exact public request property transfer, supported keyed-state compatibility transfer, dispatch to the internal relational request, reserved output publishing, and generated relational pipeline result projection
- relational request/handler pipeline: write validation, lookups, derived write state, mutation, domain events, and the relational result
For new migrations, prefer a bridge-only public handler when the public side only needs response assembly. Put
write-specific derived state on the relational pipeline instead of producing it as public keyed state. Public keyed-state
binding remains supported for compatibility and for transport-layer facts that genuinely belong before the bridge.
When the internal relational request has generated pipeline steps, the generated bridge copies that request's
StepsResults object to request.StepsResults.RelationalStepsResults. Public response assembly can read auxiliary
relational-side outputs through that nested property without moving lookup/check steps back to the public pipeline.
When a generated mediator pipeline already owns custom multi-entity behavior, migrate it to
EntityChangesCommand<TDbContext, TResponse> instead of forcing a primitive companion. This keeps lookup/check/mutation
steps visible on the public pipeline, removes only the explicit SaveChanges step, and lets UnitOfWorkBehavior own the
transaction and final save. The generator analyzer marks this deterministic path with
CodeFixKind=RelationalEntityChangesPipeline only for preserved-state cases it can prove, such as ticket-action
preservation or one shared transaction. It keeps the diagnostic non-codefixable for database-generated key reads,
intermediate flushes, get-or-create flows, and bulk item derived-state flows.
For generated pipeline migrations, ECR015 is intentionally single-DbContext. If the analyzer sees pipeline evidence
for more than one DbContext, it does not offer the deterministic migration. Transaction boundary steps are identified
semantically by following local call trees to EF Core DatabaseFacade.BeginTransaction*, DatabaseFacade.CommitTransaction*,
or IDbContextTransaction.Commit* calls. When the boundary belongs to the same DbContext as the recommended relational
command, the diagnostic emits TransactionBoundarySteps, TransactionStartSteps, and TransactionCommitSteps so EAC can
remove explicit start/commit steps without relying on step names.
For homogeneous set writes, keep the same request-first model and switch the internal relational request to one of the bulk bases:
RelationalBulkCreateCommand<...>RelationalBulkDeleteCommand<...>RelationalBulkUpdateCommand<...>
Bulk create consumes the relational request's GetItems() result. If each item needs a lookup-derived value such as a
vendor or tenant id, include that value in the typed item input before GetItems() materializes entities, derive it in
the relational lane, or choose EntityChangesCommand<TDbContext, TResponse> for a richer workflow.
Compatibility And Roadmap
Legacy relational surfaces still compile, but they are no longer the design target for new code:
[HandlerForEntity<TEntity>]remains compatibility-onlyCreateEntityCommand<Response>,UpdateEntityCommand<Response>, andDeleteEntityCommand<Response>remain compatibility-onlyConfigureGeneratedRelationalCommand(...)remains deprecated migration scaffolding- legacy
*RelationalHooksare already unsupported Eternet.Crud.Relational.Generatordiscovers the relational key path from deterministic EF metadata and explicit relational contracts instead of naming heuristics (Id,<EntityName>Id,[Key]) orIIntIdentity/IStringIdentity; the remaining cleanup is the runtime/public-surface identity coupling
The relational identity decoupling work is tracked in
docs/plans/relational/identity-decoupling-plan.md:
remove nondeterministic key and lookup heuristics from the relational lane(done)- stop treating
IIntIdentityandIStringIdentityas part of the modern relational authoring model - decouple the modern relational lane from
IIdentity - obsolete legacy relational authoring entry points and back that stance with analyzer diagnostics and migration guidance
That future wave is intentionally scoped to Crud.Relational first; it does not require broad immediate cleanup across
all of Eternet.Mediator.
Related Docs
docs/README.mdfor the organized documentation indexdocs/guides/behaviors.mdfor the current runtime model, bridge conventions, compatibility notes, and examplesdocs/guides/bulk-operations.mdfor lane selection and bulk primitive guidancedocs/guides/outbox.mdfor the explicit relational outbox modeldocs/guides/include-configuration.mdfor legacy include configuration and the modern eager-loading alternativedocs/plans/relational/identity-decoupling-plan.mdfor the next cleanup wave
Migration Note: ScopedStates Retirement
Eternet.Crud.Relational 3.0.0 aligned with the ScopedStates retirement in Eternet.Mediator.
GeneratedRelationalCommandBinderno longer depends on a publicScopedStatesservice or constructor argument.- Relational bridge binding now resolves values through
GeneratedBindingRuntimeand the ambient generated runtime scope. - Tests or custom infrastructure that previously created
ScopedStatesmanually should migrate toGeneratedBindingRuntime.EnterScope()andGeneratedBindingRuntime.Publish(...)when they need runtime state in-process.
Migration guide:
../../Eternet.Mediator/docs/scoped-states-breaking-change-v3.md
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 is compatible. 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. |
-
net10.0
- Eternet.Mediator (>= 3.0.16)
- Microsoft.EntityFrameworkCore (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection (>= 10.0.5)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.5)
- Microsoft.Extensions.Hosting (>= 10.0.5)
- Microsoft.Extensions.Logging (>= 10.0.5)
-
net9.0
- Eternet.Mediator (>= 3.0.16)
- Microsoft.EntityFrameworkCore (>= 9.0.10)
- Microsoft.Extensions.DependencyInjection (>= 9.0.10)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.10)
- Microsoft.Extensions.Hosting (>= 9.0.10)
- Microsoft.Extensions.Logging (>= 9.0.10)
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 |
|---|---|---|
| 3.1.22 | 34 | 5/14/2026 |
| 3.1.21 | 90 | 5/11/2026 |
| 3.1.20 | 96 | 5/8/2026 |
| 3.1.19 | 111 | 5/5/2026 |
| 3.1.18 | 100 | 5/4/2026 |
| 3.1.17 | 98 | 5/1/2026 |
| 3.1.16 | 85 | 5/1/2026 |
| 3.1.15 | 88 | 5/1/2026 |
| 3.1.14 | 88 | 5/1/2026 |
| 3.1.13 | 88 | 4/30/2026 |
| 3.1.12 | 131 | 4/23/2026 |
| 3.1.11 | 99 | 4/20/2026 |
| 3.1.10 | 95 | 4/20/2026 |
| 3.1.9 | 120 | 4/13/2026 |
| 3.1.8 | 130 | 4/12/2026 |
| 3.1.7 | 95 | 4/12/2026 |
| 3.1.6 | 110 | 4/10/2026 |
| 3.1.5 | 92 | 4/10/2026 |
| 3.1.4 | 94 | 4/8/2026 |
| 3.1.3 | 151 | 4/6/2026 |
Aggregate-root lane semantics: create makes new roots; update handles root-governed child mutations.