DbContext Options¶
Register the DynamoDB EF Core provider by calling UseDynamo on DbContextOptionsBuilder, then
tune read consistency, transaction limits, batch sizes, scan protection, and index selection
behavior through the options builder or per-context runtime overrides.
Registering the Provider¶
In OnConfiguring¶
Override OnConfiguring in your DbContext subclass. This is the quickest path and works well
for console apps or tests:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseDynamo();
Pass a configuration callback to set provider options at the same time:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseDynamo(options =>
{
options.ConfigureDynamoDbClientConfig(config =>
{
config.ServiceURL = "http://localhost:8000";
config.AuthenticationRegion = "us-east-1";
config.UseHttp = true;
});
});
Through Dependency Injection¶
Pass UseDynamo inside AddDbContext when you want to supply a pre-configured
IAmazonDynamoDB instance or read options from IConfiguration:
services.AddDbContext<ShopContext>(options =>
{
options.UseDynamo(o =>
{
o.DynamoDbClient(dynamoClient);
});
});
A context that configures itself via OnConfiguring can still be registered in the container —
the two approaches are not mutually exclusive:
AddEntityFrameworkDynamo() is called automatically by UseDynamo and registers all provider
services. You do not need to call it directly.
Available Options¶
Most provider options are set on the DynamoDbContextOptionsBuilder passed to the UseDynamo callback. Warning policies use EF Core's ConfigureWarnings API.
Scan-like query warning¶
Read queries that do not use equality or IN on the active partition key of the table or active
index throw by default. The provider configures DynamoEventId.ScanLikeQueryDetected as an explicit
throwing warning to prevent accidental table or index scans.
Configure this event explicitly to allow scan-like queries globally:
ConfigureWarnings(w => w.Default(...)) does not override this provider safety default. Use the
per-event ScanLikeQueryDetected setting or .AllowScan().
For one intentional scan, keep the global default and opt in on the query:
Unsafe filtered First*¶
Not a best practice
`AllowUnsafeFilteredQueries()` bypasses the provider's `First` / `FirstOrDefault` safety
validation for every query in the context. It does not disable scan-like query protection, does
not allow explicit `Limit(n)` or `WithNextToken()` with `First*`, and does not change `First*`
execution: the provider still sends one request with implicit `Limit=1` when no user limit is
specified.
DynamoDB applies filters after evaluating items, so `FirstOrDefaultAsync` can return `null`
and `FirstAsync` can throw even when a later item would match. This option is intended for
tests or controlled legacy code only.
The option is disabled by default. Enable it globally only when needed:
Prefer the per-query escape hatch when possible:
var order = await db.Orders
.Where(o => o.Pk == customerId && o.Status == "PENDING")
.AsUnsafeFilteredQuery()
.FirstOrDefaultAsync();
Consistent reads¶
Reads are eventually consistent by default. Configure strongly consistent reads globally when most queries in a context require read-after-write consistency:
Override the default for a single query with .WithConsistentRead() or
.WithConsistentRead(false):
var order = await db.Orders
.Where(o => o.Pk == customerId && o.Sk == orderId)
.WithConsistentRead()
.FirstOrDefaultAsync();
The provider passes the setting through to DynamoDB's ExecuteStatementRequest.ConsistentRead
for base-table and LSI reads. If a global default query is finalized to a GSI, the provider leaves
ConsistentRead unset because DynamoDB GSIs are always eventually consistent. An explicit
.WithConsistentRead() on a GSI query throws instead of silently weakening the query's consistency.
The provider does not warn or throw when the query is scan-like; DynamoDB applies its own semantics.
TransactionOverflowBehavior¶
When a transactional SaveChangesAsync write unit exceeds the effective MaxTransactionSize, the
provider applies the configured TransactionOverflowBehavior.
This option exists because DynamoDB caps a single ExecuteTransaction request at 100 write
operations. If your unit of work can cross that boundary (for example, aggregate updates that touch
many root entities at once), you need to decide whether the provider should fail before sending
anything or continue in smaller transactional chunks.
| Value | Behavior |
|---|---|
Throw (default) |
Throws InvalidOperationException before any writes are sent |
UseChunking |
Splits the write unit into multiple ExecuteTransaction calls |
Choose based on your consistency requirements:
- Use
Throwwhen the whole save must behave as one all-or-nothing unit. - Use
UseChunkingwhen large saves are expected and your application can recover from partially completed saves (idempotent commands, compensating actions, or explicit re-query + retry flows).
optionsBuilder.UseDynamo(options =>
{
options.TransactionOverflowBehavior(TransactionOverflowBehavior.UseChunking);
});
Chunking is not globally atomic
Each chunk is individually atomic, but the overall `SaveChangesAsync` call is not. If chunk _N_
fails, chunks _0..N-1_ are permanently committed and cannot be rolled back. Always re-query the
affected entities before retrying a chunked save.
Chunking requires acceptAllChangesOnSuccess=true
Chunked transactional saves are not supported for `SaveChangesAsync(false, ...)`. The provider
throws `InvalidOperationException` before any chunk is sent.
MaxTransactionSize¶
MaxTransactionSize caps how many write operations the provider sends in a single
ExecuteTransaction call. DynamoDB's hard maximum is 100 and the provider default is 100:
This limit applies only to transactional multi-root save paths that use ExecuteTransaction.
Single-root direct saves (ExecuteStatement) are not affected.
Lower this when you intentionally want large saves to split earlier, usually to keep transaction
size predictable or to combine with UseChunking so overflow happens at a boundary you control.
Leave it at 100 when your main goal is maximizing the amount of work that can stay in one atomic
transaction.
MaxTransactionSize must be between 1 and 100 (inclusive). Values outside this range throw
InvalidOperationException during options configuration (or when setting per-context overrides).
MaxBatchWriteSize¶
When EF Core's AutoTransactionBehavior is set to Never, multi-root saves use DynamoDB's
non-atomic BatchExecuteStatement API instead of ExecuteTransaction. MaxBatchWriteSize caps
the number of write operations per batch call (DynamoDB's hard maximum is 25):
This mainly matters when you have explicitly opted out of automatic transactions. Lower values can reduce the blast radius of a failed non-atomic batch and make large writes easier to reason about, at the cost of more round trips. Leave it at the default when throughput matters more than smaller batch boundaries.
MaxBatchWriteSize must be between 1 and 25 (inclusive).
Batched multi-root saves require acceptAllChangesOnSuccess=true
With `AutoTransactionBehavior.Never`, multi-root batched saves are not supported for
`SaveChangesAsync(false, ...)`. The provider throws `InvalidOperationException` before writes
are sent.
DynamoAutomaticIndexSelectionMode¶
By default the provider automatically routes compatible queries to a Global or Local Secondary Index when exactly one safe candidate is found:
| Mode | Behavior |
|---|---|
Off |
No automatic routing — use explicit .WithIndex("name") hints |
SuggestOnly |
Analyzes candidate indexes and emits DYNAMO_IDX* diagnostics; does not change the query |
On (default) |
Automatically routes queries to an unambiguous matching index |
Use Off when you want query behavior to stay completely explicit in application code. Use
SuggestOnly while validating a schema or rolling the feature out, because it shows where an index
would help without changing production behavior. Use On when you want automatic routing for
obvious matches, but still want the provider to avoid guessing between multiple plausible indexes.
optionsBuilder.UseDynamo(options =>
{
options.UseAutomaticIndexSelection(DynamoAutomaticIndexSelectionMode.Off);
});
See Index Selection for details on how the provider chooses an index and what diagnostics it emits.
AutoTransactionBehavior (EF Core standard)¶
AutoTransactionBehavior is a standard EF Core setting — not DynamoDB-specific — but it directly
affects which DynamoDB API the provider uses:
| Value | Single-root save behavior | Multi-root save behavior |
|---|---|---|
WhenNeeded (EF default) |
Direct ExecuteStatement |
ExecuteTransaction (or chunked ExecuteTransaction calls when overflow + UseChunking) |
Always |
Direct ExecuteStatement |
ExecuteTransaction; if root writes exceed MaxTransactionSize, throws InvalidOperationException |
Never |
Direct ExecuteStatement |
Non-atomic BatchExecuteStatement; MaxBatchWriteSize applies |
In practice, WhenNeeded is the balanced default for most apps because it preserves atomicity only
when the save shape requires it. Choose Always when you want stricter, more predictable
transaction usage for multi-root saves and prefer hard failures over fallback behavior. Choose
Never only when you explicitly want to trade atomicity for batch-style throughput.
Per-Context Runtime Overrides¶
The context.Database facade lets you override transaction and batch settings for a specific
context instance, without changing the startup configuration. Overrides take effect immediately and
apply to all subsequent SaveChangesAsync calls on that instance.
Overrides are scoped to that DbContext instance only and are discarded when the instance is
disposed.
// Override for this context instance only
context.Database.SetTransactionOverflowBehavior(TransactionOverflowBehavior.UseChunking);
context.Database.SetMaxTransactionSize(25);
context.Database.SetMaxBatchWriteSize(10);
// Read the effective value (override if set, else startup option, else provider default)
var size = context.Database.GetMaxTransactionSize();
Precedence (highest to lowest):
- Per-context override (
context.Database.Set...) - Startup option (
UseDynamo(options => ...)) - Provider default (
Throw,100,25)