{"record":{"id":"a94973846c6695c6","repo":"dotnet/orleans","slug":"grainstate-table-property-not-initialized","errorCode":null,"errorMessage":"GrainState-Table property not initialized","messagePattern":"GrainState-Table property not initialized","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs","lineNumber":67,"sourceCode":"            string name,\n            AzureTableStorageOptions options,\n            IOptions<ClusterOptions> clusterOptions,\n            ILogger<AzureTableGrainStorage> logger,\n            IActivatorProvider activatorProvider)\n        {\n            this.options = options;\n            this.clusterOptions = clusterOptions.Value;\n            this.name = name;\n            this.storageSerializer = options.GrainStorageSerializer;\n            this.logger = logger;\n            _activatorProvider = activatorProvider;\n        }\n\n        /// <summary> Read state data function for this storage provider. </summary>\n        /// <see cref=\"IGrainStorage.ReadStateAsync{T}\"/>\n        public async Task ReadStateAsync<T>(string grainType, GrainId grainId, IGrainState<T> grainState)\n        {\n            if (tableDataManager == null) throw new ArgumentException(\"GrainState-Table property not initialized\");\n\n            string pk = GetKeyString(grainId);\n            LogTraceReadingGrainState(grainType, pk, grainId, this.options.TableName);\n            string partitionKey = pk;\n            string rowKey = AzureTableUtils.SanitizeTableProperty(grainType);\n            var entity = await tableDataManager.Read(partitionKey, rowKey).ConfigureAwait(false);\n            if (entity is not null)\n            {\n                var loadedState = ConvertFromStorageFormat<T>(entity);\n                grainState.RecordExists = loadedState != null;\n                grainState.State = loadedState ?? CreateInstance<T>();\n                grainState.ETag = entity.ETag.ToString();\n            }\n            else\n            {\n                grainState.RecordExists = false;\n                grainState.ETag = null;\n                grainState.State = CreateInstance<T>();","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/Azure/Orleans.Persistence.AzureStorage/Providers/Storage/AzureTableStorage.cs#L49-L85","documentation":"Thrown by AzureTableGrainStorage.ReadStateAsync when tableDataManager is null. The tableDataManager is only assigned inside Init() (line 498) during the silo lifecycle initialization stage. If Init() never ran (wrong InitStage ordering, lifecycle not subscribed) or Close() was called (which sets it to null at line 513), the field is null and any grain state operation fails.","triggerScenarios":"A grain's ReadStateAsync is invoked before the storage provider's Init lifecycle stage has completed, or after the silo has begun shutdown and Close() has set tableDataManager to null. This indicates an InitStage misconfiguration or a storage call outside the active silo lifetime window.","commonSituations":"InitStage set to a very late stage (e.g., active) while grain activations start earlier. Storage provider was not properly wired into the lifecycle (Participate not called). Calling grain storage operations during or after silo shutdown. A custom lifecycle stage ordering that places grain activation before storage initialization.","solutions":["Check options.InitStage — it must complete before grain activations begin (default is typically ServiceLifecycleStage.ApplicationServices or earlier).","Ensure the storage provider is properly registered via AddAzureTableGrainStorage so Participate(ISiloLifecycle) is wired.","Avoid calling grain methods that trigger storage after silo.StopAsync() or during shutdown.","Verify the Init() method did not throw — check logs for LogErrorInitializationFailed, which would leave tableDataManager null."],"exampleFix":"// before: InitStage too late, grains activate before storage is ready\no.InitStage = ServiceLifecycleStage.Active;\n\n// after: Init at an early stage\no.InitStage = ServiceLifecycleStage.ApplicationServices;","handlingStrategy":"validation","validationCode":"// Ensure InitStage is set before grain services activate\nbuilder.AddAzureTableGrainStorage(\"tableStore\", o =>\n{\n    o.InitStage = ServiceLifecycleStage.ApplicationServices; // before grain activation\n});","typeGuard":null,"tryCatchPattern":"try { await grain.ReadStateAsync(); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"GrainState-Table property not initialized\"))\n{\n    logger.LogError(ex, \"Table storage not initialized — check InitStage and lifecycle wiring.\");\n    throw;\n}","preventionTips":["Set InitStage to a stage that completes before grain activations (e.g., ApplicationServices).","Check silo startup logs for initialization errors from AzureTableGrainStorage.","Never call grain storage methods after silo shutdown begins.","Verify Participate(ISiloLifecycle) is registered when using custom storage wiring."],"tags":["azure","table-storage","lifecycle","initialization","orleans","null-reference"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}