Add, Update, and Delete¶
SaveChangesAsync reads the EF Core change tracker, compiles each pending entity state change into a PartiQL statement, and sends the write to DynamoDB — only modified properties are included in UPDATE statements, and UPDATE/DELETE statements target the item's primary key.
Async only
The DynamoDB SDK does not expose synchronous write APIs. `SaveChanges` (synchronous) always
throws `NotSupportedException`. Always use `SaveChangesAsync`.
How Writes Are Compiled¶
When you call SaveChangesAsync, the provider runs a two-stage pipeline before any network call
is made:
DynamoSaveChangesPlannerwalks the change tracker and compiles each pending entity into a PartiQL statement and a parameter list. Statements are validated at this stage — if a statement exceeds the 8,192-byte limit, an exception is thrown before any write is attempted.DynamoWriteExecutorsends the compiled statements to DynamoDB via theExecuteStatement,ExecuteTransaction, orBatchExecuteStatementAPIs depending on the number of operations and the configured transaction behavior (see Transactions).
Adding Entities¶
Add an entity to a DbSet and call SaveChangesAsync. The entity must have all primary key
properties populated; if you are using GeneratedKeyProperties, the provider assigns values
before saving.
var order = new Order
{
Pk = "CUSTOMER#42",
Sk = "ORDER#2026-001",
Status = "pending",
Total = 149.99m,
};
db.Orders.Add(order);
await db.SaveChangesAsync(cancellationToken);
The provider generates a PartiQL INSERT INTO … VALUE {…} statement. All mapped scalar
properties are included in the VALUE clause as positional parameters:
INSERT statements are unconditional — there is no existence check. If an item with the same
partition key and sort key already exists in the table, DynamoDB raises an error that the
provider maps to DbUpdateException:
try
{
db.Orders.Add(new Order { Pk = "CUSTOMER#42", Sk = "ORDER#2026-001", ... });
await db.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateException ex)
{
// Item with this PK+SK already exists.
// ex.InnerException is DuplicateItemException.
}
Note
`DbUpdateException` (not `DbUpdateConcurrencyException`) is thrown for duplicate key
violations because the item already existing is a uniqueness constraint failure, not a
stale-read conflict. See [Optimistic Concurrency](concurrency.md) for the distinction.
If your application cannot guarantee key uniqueness at the application layer, perform a read-before-write to check existence before adding, or use a conditional write pattern at the DynamoDB level.
Updating Entities¶
Load an entity, mutate its properties, and call SaveChangesAsync. EF Core's change tracker
records which properties changed from their snapshot values.
var order = await db.Orders
.Where(o => o.Pk == "CUSTOMER#42" && o.Sk == "ORDER#2026-001")
.AsAsyncEnumerable()
.SingleAsync(cancellationToken);
order.Status = "shipped";
order.ShippedAt = DateTimeOffset.UtcNow;
order.LegacyField = null; // scalar null writes a DynamoDB NULL attribute
await db.SaveChangesAsync(cancellationToken);
The provider generates an UPDATE … SET … WHERE pk = ? AND sk = ? statement that includes only
the properties that actually changed:
Key behaviors:
- Only modified properties appear. Unchanged properties are omitted from the statement entirely — there is no full-document replace.
- Scalar
nullwrites DynamoDBNULL. Setting a scalar property tonullwrites an explicit{ NULL: true }attribute. Null complex properties and complex collections can be represented as removed nested attributes when the provider emits aREMOVEfor that path. - Primary keys cannot be modified. Attempting to change a
[Key]-annotated or key-mapped property on a tracked entity throwsNotSupportedException. To change an item's key, delete the existing entity and add a new one.
Complex Properties in Updates¶
Complex properties and complex collections are stored as nested attributes (sub-documents) in the same DynamoDB item. When a complex-property path changes, the provider emits a targeted SET or REMOVE clause rather than replacing the entire item:
| Complex-property change | Generated clause |
|---|---|
| Complex property added | SET "address" = ? (full sub-document) |
| Complex property removed | REMOVE "address" |
| Property inside complex property modified | SET "address"."city" = ? (nested path) |
This is different from relational providers, where related data may live in separate rows or tables. In the DynamoDB provider, every complex-property mutation targets a path within the same item.
Primitive collections inside complex properties are serialized using the same DynamoDB wire shapes
as root properties: lists become L, dictionaries become M, string/number/binary sets become
SS/NS/BS, and byte[] remains a binary scalar (B). Null list elements or dictionary
values serialize as DynamoDB NULL; null complex collections are removed when updated. DynamoDB
sets cannot be empty, contain null elements, or mix string, number, and binary member kinds.
Deleting Entities¶
Call db.Remove(entity) (or set db.Entry(entity).State = EntityState.Deleted) and then call
SaveChangesAsync.
var order = await db.Orders
.Where(o => o.Pk == "CUSTOMER#42" && o.Sk == "ORDER#2026-001")
.AsAsyncEnumerable()
.SingleAsync(cancellationToken);
db.Orders.Remove(order);
await db.SaveChangesAsync(cancellationToken);
The provider generates a DELETE FROM … WHERE pk = ? AND sk = ? statement:
Deleting a non-existent item is a silent success. DynamoDB returns success when the item identified by the WHERE predicate is not found. This is by design: the goal of a DELETE is for the item to not exist; if it is already gone, the outcome is the same. The provider does not treat this as an error.
If you have configured concurrency tokens, the token value is appended to the WHERE predicate.
If the item exists but its token has changed since the entity was loaded, DynamoDB raises a
conflict and the provider throws DbUpdateConcurrencyException. See
Optimistic Concurrency.
Statement Size Limit¶
Each PartiQL statement has an 8,192-byte size limit enforced by DynamoDB. The provider
validates the compiled statement length before executing any writes and throws
InvalidOperationException at planning time if the limit is exceeded.
Statement size limit
The limit is most likely to be hit on INSERT statements with many mapped properties or large
complex-property sub-documents. The error message reports the actual character or byte count:
```
The generated PartiQL statement is 9,841 UTF-8 bytes, which exceeds DynamoDB's
8,192-byte statement-size limit. Consider reducing the number of mapped scalar
properties or splitting the write unit across multiple SaveChanges calls.
```
To fix: reduce the number of mapped properties, split large nested documents into separate items,
or batch smaller sets of entities per `SaveChangesAsync` call.