dotnet/efcore · error · InvalidOperationException
When using AutoTransactionBehavior.Always with the Cosmos DB
Error message
When using AutoTransactionBehavior.Always with the Cosmos DB provider, all changed entities in a SaveChanges call must be in the same collection and partition and not exceed 100 entities to ensure atomicity.
What it means
When AutoTransactionBehavior is Always, the Cosmos provider must put all changed entities into a single transactional batch. Cosmos transactional batches are limited to a single container AND a single partition key AND a maximum of 100 operations. If the batch exceeds 100 entities or spans multiple containers/partitions, the provider throws (CosmosDatabaseWrapper.cs:293-298) because it cannot create a valid atomic batch.
Source
Thrown at src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs:298
if (_currentDbContext.Context.Database.AutoTransactionBehavior == AutoTransactionBehavior.Always)
{
if (singleUpdateEntries.Count >= 1)
{
if (rootEntriesToSave.Count >= 2)
{
throw new InvalidOperationException(CosmosStrings.SaveChangesAutoTransactionBehaviorAlwaysTriggerAtomicity);
}
// There is only 1 entry, and it has a trigger
return new SaveGroups { BatchableUpdateEntries = [], SingleUpdateEntries = singleUpdateEntries };
}
var firstEntry = batchableEntries[0];
var key = new Grouping(firstEntry.CollectionId, _cosmosClient.GetPartitionKeyValue(firstEntry.Entry));
return batchableEntries.Count > 100
|| !batchableEntries.All(entry =>
entry.CollectionId == key.ContainerId && _cosmosClient.GetPartitionKeyValue(entry.Entry) == key.PartitionKeyValue)
? throw new InvalidOperationException(CosmosStrings.SaveChangesAutoTransactionBehaviorAlwaysAtomicity)
: new SaveGroups { BatchableUpdateEntries = [(key, batchableEntries)], SingleUpdateEntries = [] };
}
var batches = CreateBatches(batchableEntries);
// For bulk it is important that single writes are always classified as singleUpdateEntries so that they will be executed in parallel
if (_bulkExecutionEnabled && _currentDbContext.Context.Database.AutoTransactionBehavior != AutoTransactionBehavior.Always)
{
for (var i = batches.Count - 1; i >= 0; i--)
{
var (Key, UpdateEntries) = batches[i];
if (UpdateEntries.Count == 1)
{
batches.RemoveAt(i);
singleUpdateEntries.Add(UpdateEntries[0]);
}
}
}View on GitHub (pinned to dbf9771522)
Solutions
- Group SaveChanges calls by partition key and container so each call stays within one partition and under 100 entities.
- Switch AutoTransactionBehavior to WhenNeeded (default) to allow automatic multi-batch processing without the atomicity constraint.
- Reduce the number of entities per SaveChanges call to 100 or fewer, all sharing the same partition key.
Example fix
// before
context.Database.AutoTransactionBehavior = AutoTransactionBehavior.Always;
foreach (var item in items) context.Items.Add(item); // 150 items, mixed partitions
await context.SaveChangesAsync(); // throws
// after — batch by partition, max 100 each
context.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded;
await context.SaveChangesAsync();
// or manually group:
foreach (var group in items.GroupBy(i => i.PartitionKey).Select(g => g.Take(100)))
{
// save each group in its own SaveChanges
} Defensive patterns
Strategy: validation
Validate before calling
// Before SaveChanges with Always, verify all root entries share one partition+container and are <= 100.
if (context.Database.AutoTransactionBehavior == AutoTransactionBehavior.Always)
{
var entries = context.ChangeTracker.Entries()
.Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
.ToList();
if (entries.Count > 100)
throw new InvalidOperationException("More than 100 entities with AutoTransactionBehavior.Always.");
// Further check that all share the same container + partition key value.
} Prevention
- With AutoTransactionBehavior.Always, keep each SaveChanges within one partition and under 100 entities.
- Use AutoTransactionBehavior.WhenNeeded for bulk operations across partitions.
- Group entities by partition key before saving when Always is required.
When it happens
Trigger: Setting AutoTransactionBehavior = Always and calling SaveChanges with: (a) more than 100 root document entries, or (b) entries spread across different containers, or (c) entries in different partition key values within the same container. The provider checks that all batchable entries share the same (ContainerId, PartitionKeyValue) and that the count is at most 100.
Common situations: Bulk-importing records across multiple partitions with Always enabled. Mixing entities mapped to different containers in one SaveChanges with Always. Exceeding the 100-entity batch limit during data migration.
Related errors
- When using AutoTransactionBehavior.Always with the Cosmos DB
- The entity of type '{entityType}' is mapped as part of the d
- Azure Cosmos DB does not support synchronous I/O. Make sure
- The property '{1_entityType}.{0_property}' contains null, bu
- The Cosmos database provider does not support transactions.
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/f9fba14ad01236f1.
Report an issue: GitHub.