{"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/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseWrapper.cs#L280-L316","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ncontext.Database.AutoTransactionBehavior = AutoTransactionBehavior.Always;\nforeach (var item in items) context.Items.Add(item); // 150 items, mixed partitions\nawait context.SaveChangesAsync(); // throws\n\n// after — batch by partition, max 100 each\ncontext.Database.AutoTransactionBehavior = AutoTransactionBehavior.WhenNeeded;\nawait context.SaveChangesAsync();\n// or manually group:\nforeach (var group in items.GroupBy(i => i.PartitionKey).Select(g => g.Take(100)))\n{\n    // save each group in its own SaveChanges\n}","handlingStrategy":"validation","validationCode":"// Before SaveChanges with Always, verify all root entries share one partition+container and are <= 100.\nif (context.Database.AutoTransactionBehavior == AutoTransactionBehavior.Always)\n{\n    var entries = context.ChangeTracker.Entries()\n        .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)\n        .ToList();\n    if (entries.Count > 100)\n        throw new InvalidOperationException(\"More than 100 entities with AutoTransactionBehavior.Always.\");\n    // Further check that all share the same container + partition key value.\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["cosmos","transactions","partitioning","batch-limits","savechanges"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}