Skip to content

Limitations

The DynamoDB EF Core provider does not support all standard EF Core features. This page is the authoritative reference for what is not supported, why, and what workaround (if any) applies.

NativeAOT and precompiled queries

NativeAOT query support is experimental and follows EF Core's precompiled-query restrictions. Queries must be discoverable as static LINQ expressions during the build. Queries assembled at runtime from expression trees cannot be intercepted and precompiled.

NativeAOT publishing can report trimming and dynamic-code warnings from EF Core, the AWS SDK, or provider paths outside query execution. The provider's smoke build allows those warnings while AOT support remains experimental; a warning-free trimmed application is not yet guaranteed.

CI publishes and runs the NativeAOT smoke application for both EF Core 10 and EF Core 11.

The tested native path supports scalar entity properties, including nullable numbers, Boolean, binary, configured scalar conversions, and one-dimensional arrays with non-nullable elements. Enums without an explicit converter also materialize directly (stored as their numeric DynamoDB value). List, set, and dictionary primitive collections work when their elements are themselves primitives or use their own element-level value converter (for example List<Guid> or List<SomeEnum> with string element conversion). Collections of non-primitive, non-convertible element types — complex/owned entity types aside from dedicated complex-collection support — and property-level value converters composed over the collection itself are rejected at compiled-model generation time.

NativeAOT precompiled queries do not currently support entity materialization that requires EF Core to read a non-public mapped field. This includes mutable field-backed collection properties such as List<T>, HashSet<T>, and Dictionary<string, T>; auto-properties are affected when EF Core selects their backing field. The current EF Core generated field-read accessor is invalid in NativeAOT. Keep those collection or dictionary values out of entities materialized by precompiled NativeAOT queries until EF Core resolves the issue.

Field-only properties can hit the same limitation. Basic scalar properties and arrays are covered by the native smoke test because their materialization uses a field write, not a field read.

Query execution is asynchronous only. Synchronous query operators and enumeration throw InvalidOperationException; use ToListAsync, FirstAsync, ToPageAsync, or AsAsyncEnumerable.

Limit(n) and WithNextToken(...) both precompile normally, including with runtime-varying (local-variable) arguments — see Precompiled Queries and NativeAOT for the local-variable requirement.

ToPageAsync(...) does not, and so cannot run in a NativeAOT-published binary (a query with no generated interceptor has no JIT fallback — not a build-time error; dotnet publish succeeds and the failure only surfaces at execution). This is an upstream EF Core limitation, not a gap in how this provider translates or executes pagination: ToPageAsync(...) works correctly through the provider's normal pipeline outside NativeAOT, but EF Core's precompiler discovers query roots through a closed, internal mechanism with no registration point for provider-defined terminal methods, so the call is silently skipped. Limit(...).WithNextToken(...).ToListAsync() — a recognized EF terminal — precompiles and executes correctly under NativeAOT, proving the pagination mechanics work. For a tracked, non-empty result this is enough for full pagination: EntityEntry.GetExecuteStatementResponse() exposes the page's NextToken (see Pagination), verified against a precompiled NativeAOT query. ToPageAsync(...) remains necessary for projections, no-tracking queries, and empty pages, where there is no tracked entity to read the token from. Tracked upstream: dotnet/efcore#38962. See Precompiled Queries and NativeAOT for details.

Nullable-coalescing projections such as Select(x => x.OptionalCount ?? -1) are rejected by the EF Core C#-to-LINQ translator before provider translation — this one is an intentional build-time error, not a runtime fallback. Use supported query shapes, or run either of these query shapes without precompilation.

Precompiled-query generation upstream of the provider cannot handle complex-type members: a query that materializes or filters on a complex property fails during dotnet publish with an EF Core generated-code error. Avoid complex properties in contexts precompiled for NativeAOT.

Primitive-collection properties materialize their codec from compiled-model generated code under NativeAOT. A primitive-collection property that also carries a property-level value converter is not supported on that path and fails when the compiled model is generated. Converters on the collection elements are supported.

Converted values keep one interpreted seam under NativeAOT: value-converter delegate compilation falls back to the .NET expression interpreter, matching EF Core's own NativeAOT behavior. Query translation, parameter serialization, and materialization codecs themselves use generated or hand-written code only.

On EF Core 10, precompiled query generation cannot combine constant and computed (self-referencing, for example Count + 1) setter values in a single ExecuteUpdate: EF Core's design-time generator misroutes the captured-variable extraction and would silently write the wrong value. The provider fails the build with a clear error for that shape; split the update into separate ExecuteUpdateAsync calls (one for constants, one for computed values). EF Core 11 supports the mixed shape.

Precompiled query constants and parameters must bind to a mapped entity property. The generated code resolves each value's type mapping through the property that owns it (including element mappings of primitive collections, for example Contains over a List<T> attribute). A query value whose type mapping is not associated with any mapped property fails at precompile time with an InvalidOperationException instead of silently deferring the failure to first query execution, where it would use reflection that is not supported under NativeAOT.

See Precompiled Queries and NativeAOT for supported setup and verification.

Database lifecycle

  • SaveChanges never creates DynamoDB tables. Call EnsureCreatedAsync explicitly or provision tables outside the provider.
  • Lifecycle APIs are async-only. Synchronous EnsureCreated, EnsureDeleted, and CanConnect throw NotSupportedException.
  • EnsureCreatedAsync creates missing tables with PAY_PER_REQUEST billing. It can add missing GSIs to existing on-demand tables, but cannot add GSIs to provisioned tables because lifecycle throughput configuration is not exposed yet. It cannot add LSIs after table creation. Lifecycle wait polling, backoff, timeout, and whether to wait for completion are configurable.
  • Existing schema validation is limited to table key schema and secondary-index key/projection shape.
  • Include secondary-index projection cannot be created yet because non-key projected attributes are not represented in provider metadata.

See Table Lifecycle for full behavior and seeding semantics.

Unsupported LINQ Operators

The following operators throw InvalidOperationException at translation time. The provider does not fall back to in-process evaluation for these — the exception surfaces before any DynamoDB request is sent.

See Supported Operators for the full list of what does translate.

Category Operators Why
Aggregation Count, LongCount, Sum, Average, Min, Max DynamoDB PartiQL has no query aggregate functions; primitive collection count/length is a supported size(attr) exception
Grouping GroupBy GROUP BY is not supported in DynamoDB PartiQL
Joins Join, GroupJoin, LeftJoin, RightJoin, SelectMany, DefaultIfEmpty DynamoDB does not support cross-item joins
Set operations Union, Concat, Except, Intersect Not supported in DynamoDB PartiQL
Offset / paging Skip, Take, ElementAt, ElementAtOrDefault DynamoDB has no offset semantics — use Limit(n) for an evaluation budget; direct primitive LIST ElementAt(constant) is a supported list-index exception
Element operators Any(predicate), All except inline finite collection predicate rewrites General server-side element operators are not supported; primitive collection Any() is a supported size(attr) > 0 exception
Reverse traversal Last, LastOrDefault, Reverse Requires reverse index traversal, not implemented
Deduplication Distinct SELECT DISTINCT is not supported in DynamoDB PartiQL
Type casting Cast<T> Not supported; TPH discriminator filtering via OfType<TDerived>() and is requires active discriminator metadata; GetType() checks require active discriminator metadata and are limited to exact concrete mapped entity types
Conditional skipping SkipWhile, TakeWhile Not supported
Queryable Contains over query sources Queryable.Contains(dbSet, item) Not supported; in-memory membership translates to IN, native collection membership to contains

Value-converted enum numeric casts are also rejected when compared to parameters. For example, (int)entity.Status == value is not translated if Status uses .HasConversion<string>(), because DynamoDB stores the converted string value. Compare entity.Status to an enum value directly, or map the enum numerically.

Complex property-to-property equality and equality against complex object parameters or inline complex object constants are supported.

Complex Type Equality

Complex type equality (==) translates to whole-map attribute equality in PartiQL. DynamoDB compares the entire stored map, not individual properties. If a DynamoDB item contains unmapped attributes written outside EF Core (out-of-band writes), two items that are structurally equal by CLR properties may not compare equal at the DynamoDB level because the stored maps differ.

Rely on complex type equality only when EF Core is the sole writer of those attributes. If out-of-band writes are possible, compare individual scalar properties instead.

Scalar value-converted collection membership is not translated. entity.Values.Contains(value) is supported for native DynamoDB primitive list/set attributes, but not when Values is serialized into a scalar string or blob by a property value converter. In that case DynamoDB would evaluate substring or binary containment rather than collection membership.

byte[] properties map to one DynamoDB Binary attribute. The provider translates binary length, non-empty checks, and equality (Length, parameterless Any(), and SequenceEqual). It does not translate byte-level membership or indexing (Contains(byte), [index], First()) because DynamoDB PartiQL has no byte-level operators over Binary attributes.

Native DynamoDB primitive collection attributes support a few direct operations: membership Contains(value), parameterless Any(), Count/Count()/Length, direct list indexing or ElementAt(index) with a constant numeric index, and First()/FirstOrDefault() as constant index 0. Dynamic list indexes are not supported. These translate to PartiQL contains, size, or list-index expressions. They do not enable general list-element query pipelines; predicate Any/Count, list-element filtering, ordering, paging, joins, set operations, and direct LIST projection shaping remain unsupported.

Inline finite primitive collection predicates such as new[] { 1, 2 }.Any(x => x == entity.Value) and new[] { 1, 2 }.All(x => x != entity.Value) are rewritten to finite scalar predicates. Other predicate aggregates, including Count(predicate), Min, and Max, remain unsupported server-side.

Workaround for unsupported operators: switch to AsAsyncEnumerable() before the unsupported operator to move evaluation in-process:

// ❌ Throws at translation time
var count = await context.Orders.CountAsync();

// ✅ In-process
var count = await context.Orders.AsAsyncEnumerable().CountAsync();

In-process evaluation fetches all matching pages from DynamoDB before applying the operator. Use with care on large result sets.

Take vs Limit(n)

Take(n) is not translated — use the DynamoDB-specific Limit(n) extension instead. The distinction matters: Limit(n) maps to ExecuteStatementRequest.Limit, which is an evaluation budget (DynamoDB reads up to n items then filters). It is not a result count. See Ordering and Limiting for details.

Query Shape Constraints

First / FirstOrDefault — Key-Only Safe Path

FirstAsync and FirstOrDefaultAsync set an implicit Limit=1 on the server request. Because DynamoDB counts evaluated items against Limit (not matched items), this is only safe when the WHERE clause guarantees at most one evaluation pass before a match:

  1. No user-specified Limit(n) on the query.
  2. The WHERE clause includes a partition-key equality condition.
  3. Any sort-key predicate is a valid DynamoDB key condition (=, <, <=, >, >=, BETWEEN, begins_with).

By default, filtered First* queries that fail the partition-key or sort-key safety checks throw InvalidOperationException at translation time. AsUnsafeFilteredQuery() and AllowUnsafeFilteredQueries() can bypass only that filtered First* safety validation for controlled legacy code or tests. Explicit Limit(n) combined with First* is never supported.

Sort-key filter expressions are unsafe. SK IN (...) and SK = A OR SK = B reference only key attributes but are DynamoDB filter expressions, not key conditions. Limit=1 on a filter predicate can silently miss matching rows later in the partition:

var skValues = new[] { "ORDER#1", "ORDER#2" };

// ❌ Throws — SK IN is a filter expression
await context.Orders
    .Where(x => x.Pk == pk && skValues.Contains(x.Sk))
    .FirstOrDefaultAsync(ct);

// ✅ Client-side selection via AsAsyncEnumerable()
var result = await context.Orders
    .Where(x => x.Pk == pk && skValues.Contains(x.Sk))
    .AsAsyncEnumerable()
    .FirstOrDefaultAsync(ct);

Unsafe filtered First* is not a best practice

AsUnsafeFilteredQuery() bypasses the provider's First / FirstOrDefault safety validation for one query. AllowUnsafeFilteredQueries() applies the same bypass to every query in the context.

This 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. See AWS' notes on filter expressions. Use this only for tests or controlled legacy code.

var result = await context.Orders
    .Where(x => x.Pk == pk && skValues.Contains(x.Sk))
    .AsUnsafeFilteredQuery()
    .FirstOrDefaultAsync(ct);

Exception — PK-only table. When the base table has no sort key, each partition holds at most one item and First* with a PK equality condition is always safe.

Shared-table / inheritance. The provider injects a discriminator predicate automatically. Server-side First* is safe only when the query evaluates at most one base-table item before filtering: a PK-only lookup on a PK-only table, or a PK+SK equality on a PK+SK table. By default, all other shapes throw — use AsAsyncEnumerable().FirstOrDefaultAsync(), or explicitly opt in to unsafe filtered First* behavior when you accept the DynamoDB filter-expression risk.

Single / SingleOrDefault — Key-Condition-Only

SingleAsync and SingleOrDefaultAsync are supported only for key-condition-only query shapes. The provider sends one DynamoDB request with implicit ExecuteStatementRequest.Limit = 2 and lets EF Core enforce cardinality:

  • zero returned items: SingleAsync throws, SingleOrDefaultAsync returns null / default
  • one returned item: returns that item
  • two returned items: throws Sequence contains more than one element.

Because DynamoDB Limit counts evaluated items, not matched rows, Single* does not allow non-key filters, scan-like predicates, or sort-key filter expressions. Unlike First*, there is no unsafe filtered escape hatch yet: AsUnsafeFilteredQuery() and AllowUnsafeFilteredQueries() do not bypass Single* validation.

Allowed partition-key conditions are equality and IN. Sort-key predicates are allowed only when they are DynamoDB key conditions (=, <, <=, >, >=, BETWEEN, begins_with). For shared-table inheritance queries with discriminator filters, Single* requires a base-table lookup that identifies one physical item: PK equality on a PK-only table, or PK+SK equality on a PK+SK table. Secondary-index derived-type queries can fail validation even when they include index key conditions. Explicit Limit(n) and WithNextToken() combined with Single* throw at translation time.

If DynamoDB returns any NextToken for a validated Single* query, the provider throws InvalidOperationException. This is treated as a guard against an unexpected provider/DynamoDB invariant break, not as a signal to page. Without a continuation token, two returned items use EF Core's normal Single* duplicate detection and throw Sequence contains more than one element.

Find — Primary-Key Lookup

FindAsync is supported for primary-key lookup. It checks the change tracker first and otherwise executes a base-table key-equality PartiQL query with Limit=1. It does not use secondary indexes or automatic index selection; use LINQ with .WithIndex(...) or automatic selection for secondary-index lookups.

Synchronous Find follows EF Core's normal change-tracker behavior: it can return an already tracked entity without DynamoDB I/O. If it needs to query DynamoDB, synchronous query execution throws InvalidOperationException. Use FindAsync for database lookups:

var order = await context.Orders.FindAsync([customerId, orderId], ct);

WithNextToken Cannot Combine with First* or Single*

Combining .WithNextToken(token) with FirstAsync, FirstOrDefaultAsync, SingleAsync, or SingleOrDefaultAsync throws InvalidOperationException. A seeded continuation token implies resuming an arbitrary position in a result set, which is incompatible with provider-managed terminal limits (Limit=1 for First*, Limit=2 for Single*).

OrderBy — Only Key Columns

OrderBy and OrderByDescending only accept partition-key and sort-key column expressions. Non-key attribute ordering throws at translation time. For multi-partition queries, the partition key must be the first ORDER BY column.

Automatic Index Selection — ALL Projection Only

Automatic index selection (On or SuggestOnly mode) rejects GSI/LSI candidates whose projection type is not ALL. KEYS_ONLY and INCLUDE index candidates are logged as rejected (DYNAMO_IDX005) and excluded from selection. Use an explicit .WithIndex("name") hint to route to a non-ALL index.

String Function Limitations

string.StartsWith(s) and string.Contains(s) translate to begins_with and contains in PartiQL for string, char, and StringComparison.Ordinal overloads. Other StringComparison values and CultureInfo arguments throw at translation time.

DynamoDB PartiQL does not provide ends_with, regex, substring, replace, trim, or case-conversion functions, so EndsWith, Regex, IndexOf, Substring, Replace, Trim*, ToUpper, and ToLower are not translated.

string.Length translates to DynamoDB size(attr). DynamoDB size semantics are not guaranteed to match .NET UTF-16 string.Length for all Unicode text, so avoid relying on it for non-ASCII string length rules.

SELECT * Never Emitted

The provider always emits an explicit column list. This means projected types must have all required attributes available in the index or table projection. See Projection.

Write Constraints

Synchronous SaveChanges Not Supported

SaveChanges() throws NotSupportedException. Use SaveChangesAsync().

The AWS SDK for .NET exposes only async I/O for DynamoDB; the provider does not wrap async calls synchronously to avoid deadlocks in ASP.NET Core and other async-first hosts.

Key Mutation Not Supported

Changing a primary key (partition key or sort key) value on an entity and calling SaveChangesAsync throws NotSupportedException. DynamoDB items are identified by their key attributes; updating a key requires deleting the old item and inserting a new one. The provider does not perform this two-step operation automatically — detach and re-add the entity with the new key instead.

DynamoDB Transaction Limits

DynamoDB ExecuteTransaction enforces two hard limits:

  1. Maximum 100 write statements per transaction. When AutoTransactionBehavior is WhenNeeded or Always and the save unit exceeds MaxTransactionSize (default 100, max 100), the provider throws InvalidOperationException unless TransactionOverflowBehavior.UseChunking is configured.

  2. No duplicate items within a single transaction. Writing the same DynamoDB item more than once in a single transaction throws InvalidOperationException — the provider validates this client-side before sending the request to DynamoDB.

See Transactions for configuration details.

acceptAllChangesOnSuccess: false Restrictions

Chunked transactional writes (TransactionOverflowBehavior.UseChunking) and non-atomic batched writes (AutoTransactionBehavior.Never) both require acceptAllChangesOnSuccess: true. Calling SaveChangesAsync(acceptAllChangesOnSuccess: false) with either path throws, because partial chunk commits must be accepted immediately in the change tracker to avoid replaying already-persisted writes on retry.

PartiQL Statement Length Limit

DynamoDB enforces an 8 192-byte limit on ExecuteStatement statement text. The provider validates statement length before sending and throws InvalidOperationException if the limit is exceeded. This can happen with entities that have a large number of scalar properties. Consider splitting such entities across multiple SaveChanges calls or reducing the number of mapped properties.

EF Core Bulk Operations

ExecuteUpdateAsync() is supported as a single-item, key-targeted update: the WHERE clause must equality-constrain the full primary key, the result is 1 on success and 0 when the target item is missing (verified on DynamoDB Local; the real service may report 1 for a non-matching update), and numeric self-referencing arithmetic is limited to addition and subtraction. See ExecuteUpdateAsync for the full behavior contract.

ExecuteDeleteAsync() is not implemented. Bulk deletions must be performed by loading entities, removing them from the change tracker, and calling SaveChangesAsync() per item (or by issuing a key-targeted DELETE statement against the client).

BatchExecuteStatement Partial Success

When AutoTransactionBehavior.Never is set, the provider executes writes via BatchExecuteStatement. DynamoDB executes each statement independently — a batch can partially succeed, meaning some writes commit while others fail. The provider throws if the response contains any failed operations, but successful statements within that batch have already been persisted.

Modeling Constraints

Relationships and Foreign Keys Are Not Supported

The provider does not support EF Core relationship modeling. HasOne(...), HasMany(...), WithOne(...), WithMany(...), HasForeignKey(...), skip navigations, and relationship attributes such as [ForeignKey] and [InverseProperty] throw during model building or model validation.

DynamoDB has no relational foreign-key enforcement or joins. Model embedded document data with EF Core complex types, and model separate DynamoDB items or tables as separate root entity types without EF navigation relationships.

// ✅ Embedded data: complex types
modelBuilder.Entity<Customer>(b =>
{
    b.ComplexProperty(x => x.Profile);
    b.ComplexCollection(x => x.Contacts);
});

// ❌ Not supported: relational navigation/foreign-key modeling
modelBuilder.Entity<Order>()
    .HasOne(x => x.Customer)
    .WithMany(x => x.Orders)
    .HasForeignKey(x => x.CustomerId);

Owned Entity Types Are Not Supported

The provider does not support EF Core owned entity types. OwnsOne(...), OwnsMany(...), and other owned-type configuration paths throw during model validation with guidance to switch to complex types instead.

Use EF Core complex types for embedded document data:

modelBuilder.Entity<Customer>(b =>
{
    b.ComplexProperty(x => x.Profile);
    b.ComplexCollection(x => x.Contacts);
});

Key Configuration

Root entities may use HasPartitionKey(...)/HasSortKey(...), EF HasKey(...), [Key], [PrimaryKey], or conventions. DynamoDB table keys are limited to one partition key plus optional sort key; EF keys with more than two properties are rejected.

Key properties must be non-nullable and resolve to a DynamoDB key-compatible provider type: string, a numeric type (int, long, decimal, etc.), or byte[]. bool key properties are rejected — bool has no built-in converter to a key-compatible type. Other non-primitive types such as Guid, DateTime, and enum work because EF Core's built-in converters map them to key-compatible store types (for example Guid/DateTime to string, and enum to its numeric underlying value).

All entity types mapped to the same table must agree on key shape (PK-only or PK+SK) and must use identical physical attribute names for the partition key and sort key.

See Entities and Keys.

Secondary-Index Key Constraints

Secondary-index key properties follow the same type requirements as table keys but may be nullable (items without a scalar key-compatible value for a GSI/LSI key attribute are simply not indexed).

Local secondary indexes additionally require the table to define a sort key.

See Secondary Indexes.

Primitive Collection CLR Shapes

Primitive collection properties are supported only for specific CLR shapes. Custom or derived collection types throw during model validation.

Collection kind Supported CLR shapes
List T[], List<T>, IList<T>
Set HashSet<T>, ISet<T>, IReadOnlySet<T>
Dictionary Dictionary<string, TValue>, IDictionary<string, TValue>, IReadOnlyDictionary<string, TValue>, ReadOnlyDictionary<string, TValue>

Dictionary keys must be string. Non-string-keyed dictionary types are not supported.

Complex collection properties use a narrower CLR shape set than primitive collections. Complex collections support only List<T> and IList<T>. ICollection<T>, IReadOnlyList<T>, and arrays are not supported for complex collections. See Complex Types for complex collection mapping details.

Concurrency Tokens — Application-Managed Only

Concurrency tokens (IsConcurrencyToken() / [ConcurrencyCheck]) are supported, but the provider does not generate or increment token values automatically. Your application code must update the token value before calling SaveChangesAsync.

IsRowVersion() and ValueGenerated.OnAddOrUpdate throw during model validation because the provider cannot guarantee auto-increment semantics on DynamoDB item writes.

Discriminator Constraints

A discriminator is configured by default for every mapped entity type. Shared-table mappings and inheritance rely on it for type-safe filtering and materialization. The following constraints are validated at startup:

  • Discriminator values must be unique within the table group.
  • All entity types in the group must use the same discriminator attribute name.
  • The discriminator attribute name must not collide with any mapped property attribute name (partition key, sort key, or regular properties).

The default discriminator attribute name is $type. Existing tables without this attribute must be backfilled or mapped with HasNoDiscriminator().

See Single-Table Design.

Behavioral Differences from Standard EF Core

Synchronous execution

Normal and generated precompiled synchronous query methods throw InvalidOperationException. Use async query methods for database access. Synchronous writes remain unsupported; use SaveChangesAsync(). Find() can still return an already-tracked entity without querying.

ToQueryString() Is Debug-Only

IQueryable<T>.ToQueryString() returns generated PartiQL and formatted parameter comments without sending a request to DynamoDB. It does not execute scan warnings, log command events, or validate that DynamoDB accepts the statement at runtime. See Diagnostics and Logging.

Parameterized Null Inconsistency

When a nullable variable is null at runtime in a comparison (x.Prop == someVar where someVar is null), the provider parameterizes the query as WHERE "Prop" = ? with an AttributeValue { NULL = true }. This matches attributes stored with the DynamoDB NULL type but does not match MISSING attributes (attributes absent from the item).

By contrast, a constant null comparison (x.Prop == null) translates to "Prop" IS NULL OR "Prop" IS MISSING, which covers both representations.

DynamoDB PartiQL does not support attr IS ? (parameterized IS), so the two behaviors cannot be unified. If you need to match both NULL and MISSING via a runtime variable, use explicit functions:

// Explicit: matches both NULL and MISSING at runtime
.Where(x => EF.Functions.IsNull(x.Prop) || EF.Functions.IsMissing(x.Prop))

Two-Column Nullable Comparison

Comparing two nullable columns directly (x.A == x.B where both are nullable) generates a binary = predicate. When either column holds a NULL type or is MISSING, DynamoDB PartiQL returns MISSING (not TRUE) for the equality comparison — the row is excluded from results. There is no provider-level workaround for this shape.

Consistent Read Semantics Follow the Final Query Source

The provider can set ExecuteStatementRequest.ConsistentRead globally with options.ConsistentRead(true) or per query with .WithConsistentRead(). Per-query settings take precedence, including .WithConsistentRead(false) overriding a global strongly consistent default.

Strong consistency is sent only when the finalized query source is the base table or an LSI. If a global strongly consistent default query is finalized to a GSI through explicit index routing or automatic index selection, the provider leaves ConsistentRead unset because DynamoDB GSIs are always eventually consistent. If a query explicitly calls .WithConsistentRead() and the finalized source is a GSI, the provider throws before sending the request.

The provider does not warn or fail for scan-like queries. It passes allowed consistency settings through to DynamoDB and lets DynamoDB apply the service semantics for the specific statement.

Per-Entity Response Metadata Requires Tracking

context.Entry(entity).GetExecuteStatementResponse() returns null for entities loaded via AsNoTracking(). The ExecuteStatementResponse is stored in a shadow property that only exists on tracked entity entries. See Diagnostics and Logging.

Owned Types in Select Project the Full Container

Accessing a nested owned property path in a Select projection (x.Profile.Address.City) triggers client-side extraction: the full owned container attribute ("Profile") is fetched from DynamoDB and the nested value is read in-process. The path does translate server-side in Where predicates.

See Also