Skip to content

explore: low-level DynamoDB API access from DbContext #177

Description

@j-d-ha

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 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:

  • GetItem — key lookup without going through ExecuteStatement (also covered by FindAsync story 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 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

  1. 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)
  2. 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.
  3. 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.
  4. 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"

What is NOT in scope

Acceptance criteria (for the exploration phase)

  • Decision recorded on access point shape
  • Decision recorded on wrapping depth
  • GetDynamoClient() (or equivalent) implemented if the exploration confirms the thin-client approach
  • Documentation note on change tracker / concurrency implications
  • Follow-up stories created or story closed as "expose client only"

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: featNew feature💡 ideaIdea or proposal for future work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions