{"record":{"id":"61dcbc74bdf63e96","repo":"dotnet/orleans","slug":"only-dynamodb-put-and-delete-operations-are-suppor","errorCode":null,"errorMessage":"Only DynamoDB put and delete operations are supported.","messagePattern":"Only DynamoDB put and delete operations are supported\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs","lineNumber":687,"sourceCode":"        private static bool IsStorageConflict(TransactionCanceledException exception)\n        {\n            return exception.CancellationReasons?.Any(\n                reason => string.Equals(reason.Code, \"ConditionalCheckFailed\", StringComparison.Ordinal)) is true\n                || exception.Message.Contains(\"ConditionalCheckFailed\", StringComparison.Ordinal);\n        }\n\n        private static int GetOperationSize(TransactWriteItem operation, int? affectedItemSize)\n        {\n            if (operation.Put?.Item is { } item)\n            {\n                return ValidateItemSize(item, \"state\");\n            }\n            if (operation.Delete is not null)\n            {\n                return affectedItemSize ?? MaxDynamoDBItemSize;\n            }\n\n            throw new InvalidOperationException(\"Only DynamoDB put and delete operations are supported.\");\n        }\n    }\n\n    [LoggerMessage(\n        Level = LogLevel.Debug,\n        Message = \"{Partition} Loaded v0, fresh\"\n    )]\n    private partial void LogDebugLoadedV0Fresh(string partition);\n\n    [LoggerMessage(\n        Level = LogLevel.Critical,\n        Message = \"{Partition} {Error}\"\n    )]\n    private partial void LogCriticalPartitionError(string partition, string error);\n\n    [LoggerMessage(\n        Level = LogLevel.Error,\n        Message = \"{Message}\"","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs#L669-L705","documentation":"Thrown by the static BatchOperation.GetOperationSize helper when a TransactWriteItem is neither a Put (with an Item) nor a Delete. The DynamoDB transactional batch only ever queues puts and deletes, so any other TransactWriteItem type (Update, ConditionCheck) hits this guard. In practice this is an internal invariant violation, not a normal user path, because all callers (Add methods) construct Put/Delete items.","triggerScenarios":"Produced if code adds a TransactWriteItem whose Put is null and whose Delete is null to the BatchOperation (via reflection, a fork/patch, or a future code path that uses Update/ConditionCheck). Triggered by an internal programming error or a custom extension that injects a different operation type.","commonSituations":"Custom fork of the provider adding Update/ConditionCheck operations without updating GetOperationSize; reflection-based unit tests feeding arbitrary items; an upgrade that changed how operations are constructed.","solutions":["Stop feeding non-Put/non-Delete TransactWriteItems to the batch; DynamoDB TransactWriteItems in this provider are modeled as put-or-delete.","If you genuinely need Update/ConditionCheck, extend GetOperationSize (and GetItemSize) to account for their size and add the corresponding branch."],"exampleFix":"// before: a custom op path pushes an Update\nbatch.Add(new TransactWriteItem { Update = updateExpr }, ...);\n\n// after: express the change as a Put of the fully-formed item, as the provider expects\nbatch.Add(new TransactWriteItem { Put = new Put { TableName = table, Item = fullItem } }, ...);","handlingStrategy":"type-guard","validationCode":"// Only ever enqueue Put/Delete items, which is what the provider supports\nstatic TransactWriteItem AsPut(string table, Dictionary<string,AttributeValue> item) =>\n    new() { Put = new Put { TableName = table, Item = item } };","typeGuard":"static bool IsSupportedBatchOp(TransactWriteItem op) => op.Put?.Item is not null || op.Delete is not null;","tryCatchPattern":"try { await grain.TxUpdate(); }\ncatch (InvalidOperationException ix) when (ix.Message.Contains(\"Only DynamoDB put and delete operations are supported\"))\n{\n    _logger.LogCritical(\"Internal: unsupported TransactWriteItem pushed into the transactional batch\");\n    throw;\n}","preventionTips":["Always construct TransactWriteItems as Put or Delete in custom provider extensions.","If you need Update/ConditionCheck, extend GetOperationSize and GetItemSize rather than bypassing them.","Avoid reflection-based code that injects arbitrary TransactWriteItem types into the batch.","Unit-test custom operation construction against IsSupportedBatchOp before enqueueing."],"tags":["dynamodb","transactions","programming-error","invariant"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}