{"record":{"id":"f9fba14ad01236f1","repo":"dotnet/efcore","slug":"when-using-autotransactionbehavior-always-with-the-f9fba1","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs","lineNumber":298,"sourceCode":"        if (_currentDbContext.Context.Database.AutoTransactionBehavior == AutoTransactionBehavior.Always)\n        {\n            if (singleUpdateEntries.Count >= 1)\n            {\n                if (rootEntriesToSave.Count >= 2)\n                {\n                    throw new InvalidOperationException(CosmosStrings.SaveChangesAutoTransactionBehaviorAlwaysTriggerAtomicity);\n                }\n\n                // There is only 1 entry, and it has a trigger\n                return new SaveGroups { BatchableUpdateEntries = [], SingleUpdateEntries = singleUpdateEntries };\n            }\n\n            var firstEntry = batchableEntries[0];\n            var key = new Grouping(firstEntry.CollectionId, _cosmosClient.GetPartitionKeyValue(firstEntry.Entry));\n            return batchableEntries.Count > 100\n                || !batchableEntries.All(entry =>\n                    entry.CollectionId == key.ContainerId && _cosmosClient.GetPartitionKeyValue(entry.Entry) == key.PartitionKeyValue)\n                    ? throw new InvalidOperationException(CosmosStrings.SaveChangesAutoTransactionBehaviorAlwaysAtomicity)\n                    : new SaveGroups { BatchableUpdateEntries = [(key, batchableEntries)], SingleUpdateEntries = [] };\n        }\n\n        var batches = CreateBatches(batchableEntries);\n\n        // For bulk it is important that single writes are always classified as singleUpdateEntries so that they will be executed in parallel\n        if (_bulkExecutionEnabled && _currentDbContext.Context.Database.AutoTransactionBehavior != AutoTransactionBehavior.Always)\n        {\n            for (var i = batches.Count - 1; i >= 0; i--)\n            {\n                var (Key, UpdateEntries) = batches[i];\n                if (UpdateEntries.Count == 1)\n                {\n                    batches.RemoveAt(i);\n                    singleUpdateEntries.Add(UpdateEntries[0]);\n                }\n            }\n        }","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/dotnet/efcore/blob/3a2006ef569de08368d59db5e1468aa8f407e4f8/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs#L280-L316","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ndbContext.Database.AutoTransactionBehavior = AutoTransactionBehavior.Always;\nforeach (var item in manyItems) dbContext.Add(item); // >100 or mixed partitions\nawait dbContext.SaveChangesAsync();\n\n// after\nforeach (var chunk in manyItems.Chunk(100))\n{\n    // ensure all in chunk share partition key + container\n    using var scope = new DbContextScope(dbContext);\n    foreach (var item in chunk) scope.Ctx.Add(item);\n    await scope.Ctx.SaveChangesAsync();\n}","handlingStrategy":"validation","validationCode":"// Before SaveChangesAsync, enforce Always limits\nif (dbContext.Database.AutoTransactionBehavior == AutoTransactionBehavior.Always)\n{\n    if (dbContext.ChangeTracker.Entries().Count(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted) > 100)\n        throw new InvalidOperationException(\"Chunk into <=100 entities per save under Always.\");\n    // also assert all entries share one container + partition key\n}","typeGuard":null,"tryCatchPattern":"try { await dbContext.SaveChangesAsync(ct); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"same collection and partition\"))\n{\n    // re-batch: group by container/partition, chunk to <=100, then save each group.\n}","preventionTips":["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."],"tags":["cosmos","autotransactionbehavior","partition-key","transactional-batch","savechanges"],"backgroundTag":null,"analyzedSha":"3a2006ef569de08368d59db5e1468aa8f407e4f8","analyzedAt":"2026-08-11T23:42:04.146Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}