ADR-0016: Identified relationships and native Cypher results¶
Date: 2026-10-11. Status: accepted; initial scope verified in hosted CI.
M0–M3 passed the audited provider gates. M4 extends the same EF model, materializers, state manager and transaction manager; it introduces no additional entity state owner.
Identified relationships¶
ToGraphIdentifiedRelationship(type, keyProperty, sourceForeignKeyProperties, targetForeignKeyProperties) selects a single required application primary key, separate from the two required endpoint foreign keys. The ID is stored on the relationship. Endpoint values continue to come from node primary-key properties. Parallel edges sharing an endpoint pair are valid when their IDs differ. Each identified relationship type has exactly one EF mapping owner and cannot overlap a pair-association type.
Existing exact relationship-ID uniqueness and endpoint-node uniqueness constraints are required. Runtime operations inspect schema without creating it. New edges use CREATE; updates/deletes match the application ID and original endpoints, verify cardinality and optimistic tokens, and retain atomic EF entry counts. Endpoint rebinding, identified-edge skip navigations and relationships as endpoints are rejected. Model a node explicitly when another relationship needs to target the modeled object.
Read diagnostics count physical relationships globally by type and ID before filtering/paging; pair multiplicity is irrelevant to identified edges. Scalar projections retain hidden ID and endpoint slots. EF performs shaping, identity resolution and reference/inverse fix-up. This does not enable Include, relationship aggregates, general navigation predicates or tracked variable-length paths.
Identity conversions must round-trip in both directions: PK/FK writes must preserve the model value, and decoded key reads must reproduce the exact wire representation. This shared guard also applies to existing node and association keys; otherwise distinct stored Guid spellings or lossy converters could collapse into one EF identity despite a database uniqueness constraint. Custom resolved key comparers are rejected when they differ from the provider type mapping's key comparer. Identified IDs initially support required Guid/string/int/long values; composite edge IDs and empty/whitespace identified string IDs are unsupported. Ordinary payload conversion semantics are unchanged.
Native DTO/path lane¶
Database.QueryCypherAsync<T>(cypher, parameters, project, maxRows, cancellationToken) buffers a capped result using an explicit IRecord projector. Callers supply Cypher and a parameter dictionary; the provider does not build a second query language or infer entity tracking. A top-level dictionary copy prevents later key/value replacement from changing the submitted request; nested values must not be mutated in flight.
The helper enters EF's concurrency detector and borrows the current Neo4j transaction when present. A separate context-scoped native-operation lease rejects recursive projector calls, because EF's logical-flow concurrency detector is intentionally reentrant. Otherwise it owns an explicit transaction, committing only after successful buffering/projection and cancellation checks. Owned errors attempt rollback/cleanup while preserving the primary exception. Borrowed transaction errors leave its lifecycle with the caller, potentially with native writes pending. Native writes do not refresh already tracked EF values.
The row cap limits client buffering, not server work or individual row size. Driver operations do not accept cancellation tokens; cancellation is observed between awaited operations without abandoning them. Successful commit is not followed by a cancellation check. A failed commit response may have an unknown server outcome; there are no automatic retries. The helper logs neither query text nor parameter values, but propagates driver/projector exceptions. DTOs and driver paths remain untracked.
Acceptance passes for real EF parallel-edge CRUD/concurrency, ID/schema/error guards, identity/fix-up and scalar reads, plus native DTO/path/edge-existence queries, transaction sharing, cancellation and cleanup. Hosted run 38105801021, implementation a8a92d6, passes a clean solution build, 280 unit tests and 106 live tests, including 64 new unit and 26 new live cases. The preceding run caught a test-only protected DbContext constructor call, corrected before acceptance. No local heavy M4 run was attempted under the memory reserve. See the tracker (maintainer-local record) for remaining phases.