dotnet/efcore · error · InvalidOperationException
When using AutoTransactionBehavior.Always with the Cosmos…
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
With AutoTransactionBehavior.Always, EF Core wraps the batch into one Cosmos transactional batch to keep it atomic. A Cosmos transactional batch is limited to 100 operations and must target a single logical partition key in one container. If the batchable entries exceed 100 or span multiple containers/partition keys, the provider cannot keep them atomic and throws.
Solutions
- Reduce the batch to 100 or fewer entities per SaveChanges call.
- Ensure every entity in the batch shares the same container and the same partition-key value.
- Switch AutoTransactionBehavior away from Always for cross-partition or large bulk operations.
Example fix
// before
dbContext.Database.AutoTransactionBehavior = AutoTransactionBehavior.Always;
foreach (var item in manyItems) dbContext.Add(item); // >100 or mixed partitions
await dbContext.SaveChangesAsync();
// after
foreach (var chunk in manyItems.Chunk(100))
{
// ensure all in chunk share partition key + container
using var scope = new DbContextScope(dbContext);
foreach (var item in chunk) scope.Ctx.Add(item);
await scope.Ctx.SaveChangesAsync();
} Defensive patterns
Strategy: validation
Validate before calling
// Before SaveChangesAsync, enforce Always limits
if (dbContext.Database.AutoTransactionBehavior == AutoTransactionBehavior.Always)
{
if (dbContext.ChangeTracker.Entries().Count(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted) > 100)
throw new InvalidOperationException("Chunk into <=100 entities per save under Always.");
// also assert all entries share one container + partition key
} Try / catch
try { await dbContext.SaveChangesAsync(ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("same collection and partition"))
{
// re-batch: group by container/partition, chunk to <=100, then save each group.
} Prevention
- Under AutoTransactionBehavior.Always, keep batches <=100 and within one partition.
- Switch to Never for large or cross-partition bulk inserts.
- Group entities by partition key before saving.
When it happens
Trigger: Calling SaveChangesAsync with AutoTransactionBehavior == Always where either batchableEntries.Count > 100 OR the entries are not all in the same container and partition key. See CosmosDatabaseWrapper.cs:295-298.
Common situations: Bulk-inserting >100 documents with Always set. Saving entities mapped to different Cosmos containers, or different partition-key values, in one SaveChanges call while demanding atomicity.
Related errors
- When using AutoTransactionBehavior.Always with the Cosmos…
- A partition key is defined on entity type
- Azure Cosmos DB does not support synchronous I/O. Make sure…
- The entity of type ' ' is mapped as a part of the document…
- The entity of type ' ' is mapped as part of the document…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/f9fba14ad01236f1.
Report an issue: GitHub.
Appendix: 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 3a2006ef56)