{"record":{"id":"264a1dbb9f01d463","repo":"dotnet/orleans","slug":"unable-to-convert-from-storage-format-grainstateen-264a1d","errorCode":null,"errorMessage":"Unable to convert from storage format GrainStateEntity.Data={0}","messagePattern":"Unable to convert from storage format GrainStateEntity\\.Data=(.+?)","errorType":"exception","errorClass":"AggregateException","httpStatus":null,"severity":"error","filePath":"src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs","lineNumber":355,"sourceCode":"            catch (Exception exc)\n            {\n                var sb = new StringBuilder();\n                if (binaryData.Length > 0)\n                {\n                    sb.AppendFormat(\"Unable to convert from storage format GrainStateEntity.Data={0}\", binaryData);\n                }\n                else if (!string.IsNullOrEmpty(stringData))\n                {\n                    sb.AppendFormat(\"Unable to convert from storage format GrainStateEntity.StringData={0}\", stringData);\n                }\n\n                if (dataValue != null)\n                {\n                    sb.AppendFormat(\"Data Value={0} Type={1}\", dataValue, dataValue.GetType());\n                }\n\n                LogErrorSimpleMessage(sb, exc);\n                throw new AggregateException(sb.ToString(), exc);\n            }\n\n            return dataValue;\n        }\n\n        private string GetKeyString(GrainId grainId)\n        {\n            var key = $\"{clusterOptions.ServiceId}_{grainId}\";\n            return AzureTableUtils.SanitizeTableProperty(key);\n        }\n\n        private partial class GrainStateTableDataManager\n        {\n            public string TableName { get; private set; }\n            private readonly AzureTableDataManager<TableEntity> tableManager;\n            private readonly ILogger logger;\n\n            public GrainStateTableDataManager(AzureStorageOperationOptions options, ILogger logger)","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs#L337-L373","documentation":"Thrown by ConvertFromStorageFormat when deserialization of stored grain state fails. The method reads binary Data and/or string StringData columns from the TableEntity, constructs a BinaryData input, and attempts to deserialize it to type T. If deserialization throws, the exception is wrapped with diagnostic info (Data, StringData, data type) into an AggregateException.","triggerScenarios":"A grain's persisted state in Azure Table was serialized with a different serializer or schema than what the current grain type expects. This happens when the grain state class T changed shape (fields renamed/removed), the storage serializer was changed (e.g., from JSON to binary), or the data was written by a different version of the application.","commonSituations":"Deploying a new version of the grain class with incompatible state schema. Changing the configured GrainStorageSerializer without migrating existing data. Corrupted data in the table. Migrating from one serialization format to another. Version skew during rolling deployments.","solutions":["Ensure backward-compatible grain state changes — use [OrleansConstructor] and optional fields, or implement IOnDeserialized for migration.","Keep the same GrainStorageSerializer configuration across deployments.","Use a custom IGrainStorageSerializer that handles versioning and migration of old formats.","If the data is irrecoverable, delete the stale table entity so the grain starts fresh (data loss)."],"exampleFix":"// before: breaking schema change\npublic class MyGrainState { public int Count; }\n// changed to:\npublic class MyGrainState { public int Total; } // deserialization of old data fails\n\n// after: keep old field and add migration\npublic class MyGrainState\n{\n    public int Count; // keep for backward compat\n    public int Total;\n}","handlingStrategy":"try-catch","validationCode":"// Before deploying a schema change, test deserialization against a sample of existing data\nvar testData = File.ReadAllBytes(\"sample-entity.bin\");\ntry { var result = storageSerializer.Deserialize<MyGrainState>(new BinaryData(testData)); }\ncatch (Exception ex) { logger.LogError(ex, \"Deserialization will fail for existing data — add migration.\"); }","typeGuard":null,"tryCatchPattern":"try { await grain.ReadStateAsync(); }\ncatch (AggregateException ex) when (ex.Message.Contains(\"Unable to convert from storage format\"))\n{\n    logger.LogError(ex, \"Stored grain state cannot be deserialized — schema or serializer mismatch.\");\n    // Consider deleting the stale entity and re-initializing if data loss is acceptable\n    throw;\n}","preventionTips":["Make grain state changes backward-compatible (keep old fields, add new ones with defaults).","Never change the GrainStorageSerializer configuration without a data migration plan.","Use [Id(n)] attributes consistently with Orleans.Serialization to maintain stable field ordering.","Test deserialization of old data before deploying schema changes."],"tags":["azure","table-storage","deserialization","serialization","schema-mismatch","versioning","orleans"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}