You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is an exploratory story. The goal is to decide the right shape for exposing DynamoDB's low-level API surface to users who need operations that have no EF Core analogue — GetItem, Query, Scan, BatchGetItem, TransactGetItems, PutItem, UpdateItem, DeleteItem — without going through the LINQ/PartiQL pipeline at all.
The DynamoDB EF Core provider already has IDynamoClientWrapper (internal) and exposes IAmazonDynamoDB via IDynamoClientWrapper.Client. The question is: what is the right public-facing shape for users who need to drop down to the raw AWS SDK?
Problem statement
EF Core's FromPartiQL (#175) handles raw PartiQL SELECT queries. But some DynamoDB access patterns have no PartiQL equivalent or are more naturally expressed using the native API:
Query — key-based range queries with KeyConditionExpression, which maps to PartiQL but with subtly different semantics around GSI/LSI
Scan — full table scan with filter expressions; technically possible via PartiQL but Scan has its own semantics
BatchGetItem — multi-key fetch in a single call; no PartiQL equivalent
TransactGetItems — transactional consistent reads; no PartiQL equivalent
PutItem / UpdateItem / DeleteItem — conditional writes and atomic updates with ConditionExpression / update expressions; these have no EF Core model analogue
Design questions to resolve
Where does the access point live?
Option A: Extension method on DatabaseFacade (e.g. context.Database.GetDynamoClient()) — consistent with how relational providers expose GetDbConnection()
Option B: Inject IDynamoOperations (a new thin public wrapper) via DI alongside the context
Option C: Expose IAmazonDynamoDB directly with a helper method (lowest friction, highest coupling to AWS SDK)
How much wrapping is appropriate?
Raw IAmazonDynamoDB: no overhead, but users must build their own request/response objects. Acceptable since this is an escape hatch.
Thin typed wrapper: could offer strongly-typed helpers that still accept native AWS SDK request types. Adds surface area for minimal gain.
Recommendation: expose IAmazonDynamoDB directly for now; wrap only if a clear pattern emerges.
Change tracker interaction
Raw API calls bypass EF Core entirely — entities fetched this way will not be tracked
Should the escape hatch offer any opt-in tracking (e.g. attach a fetched item to the state manager)? Probably not in v1 — document the behavior.
Should DML operations be covered here or left entirely to the user?
Raw write operations (PutItem, UpdateItem, DeleteItem) bypass optimistic concurrency, shadow properties, and auditing — significant footgun
Recommendation: expose but document the risks clearly; do not integrate with change tracking
Prior art
Relational providers: context.Database.GetDbConnection() returns the underlying DbConnection. Users can then execute arbitrary SQL. No EF wrapping.
Cosmos provider: context.Database.GetCosmosClient() returns the raw CosmosClient. Same pattern — raw client, no wrapping.
DynamoDB provider today: IDynamoClientWrapper.Client exposes IAmazonDynamoDB but it's an internal interface; users cannot reach it without reflection or service locator hacks.
The Cosmos GetCosmosClient() pattern is the right precedent. A GetDynamoClient() extension on DatabaseFacade returning IAmazonDynamoDB is probably the minimal, correct answer.
Proposed outcome of exploration
At the end of this story, produce:
A decision on the access point shape (likely DatabaseFacade extension → IAmazonDynamoDB)
A decision on whether any thin typed helpers are warranted for common patterns (BatchGetItem, TransactGetItems)
An inventory of which low-level operations need documentation guidance (footguns, tracking implications)
Concrete follow-up stories for any helpers deemed worth building, or close this as "expose client only"
Purpose
This is an exploratory story. The goal is to decide the right shape for exposing DynamoDB's low-level API surface to users who need operations that have no EF Core analogue —
GetItem,Query,Scan,BatchGetItem,TransactGetItems,PutItem,UpdateItem,DeleteItem— without going through the LINQ/PartiQL pipeline at all.The DynamoDB EF Core provider already has
IDynamoClientWrapper(internal) and exposesIAmazonDynamoDBviaIDynamoClientWrapper.Client. The question is: what is the right public-facing shape for users who need to drop down to the raw AWS SDK?Problem statement
EF Core's
FromPartiQL(#175) handles raw PartiQL SELECT queries. But some DynamoDB access patterns have no PartiQL equivalent or are more naturally expressed using the native API:GetItem— key lookup without going throughExecuteStatement(also covered byFindAsyncstory feat: support FindAsync for single-item retrieval by primary key #175, but users may want it without the EF entity pipeline)Query— key-based range queries withKeyConditionExpression, which maps to PartiQL but with subtly different semantics around GSI/LSIScan— full table scan with filter expressions; technically possible via PartiQL butScanhas its own semanticsBatchGetItem— multi-key fetch in a single call; no PartiQL equivalentTransactGetItems— transactional consistent reads; no PartiQL equivalentPutItem/UpdateItem/DeleteItem— conditional writes and atomic updates withConditionExpression/ update expressions; these have no EF Core model analogueDesign questions to resolve
Where does the access point live?
DatabaseFacade(e.g.context.Database.GetDynamoClient()) — consistent with how relational providers exposeGetDbConnection()IDynamoOperations(a new thin public wrapper) via DI alongside the contextIAmazonDynamoDBdirectly with a helper method (lowest friction, highest coupling to AWS SDK)How much wrapping is appropriate?
IAmazonDynamoDB: no overhead, but users must build their own request/response objects. Acceptable since this is an escape hatch.IAmazonDynamoDBdirectly for now; wrap only if a clear pattern emerges.Change tracker interaction
Should DML operations be covered here or left entirely to the user?
PutItem,UpdateItem,DeleteItem) bypass optimistic concurrency, shadow properties, and auditing — significant footgunPrior art
context.Database.GetDbConnection()returns the underlyingDbConnection. Users can then execute arbitrary SQL. No EF wrapping.context.Database.GetCosmosClient()returns the rawCosmosClient. Same pattern — raw client, no wrapping.IDynamoClientWrapper.ClientexposesIAmazonDynamoDBbut it's an internal interface; users cannot reach it without reflection or service locator hacks.The Cosmos
GetCosmosClient()pattern is the right precedent. AGetDynamoClient()extension onDatabaseFacadereturningIAmazonDynamoDBis probably the minimal, correct answer.Proposed outcome of exploration
At the end of this story, produce:
DatabaseFacadeextension →IAmazonDynamoDB)BatchGetItem,TransactGetItems)What is NOT in scope
FindAsyncviaGetItemwithin the EF pipeline — separate story (feat: support FindAsync for single-item retrieval by primary key #175 FindAsync story)FromPartiQL— separate story (feat: FromPartiQL escape hatch for raw PartiQL SELECT queries #176 PartiQL escape hatch story)Acceptance criteria (for the exploration phase)
GetDynamoClient()(or equivalent) implemented if the exploration confirms the thin-client approach