{"record":{"id":"9b829f8cd97d1e95","repo":"dotnet/orleans","slug":"recovered-name-item-i-does-not-match-written","errorCode":null,"errorMessage":"Recovered {name} item {i} does not match. Written: {Serialize(written[i])}, recovered: {Serialize(recovered[i])}.","messagePattern":"Recovered (.+?) item (.+?) does not match\\. Written: (.+?), recovered: (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"samples/JournalingAzureBlobJson/JournalingAzureBlobJson/Program.cs","lineNumber":131,"sourceCode":"        EnsureEqual(\"tags\", written.Tags, recovered.Tags, static (left, right) => string.Equals(left, right, StringComparison.Ordinal));\n        EnsureEqual(\"balance\", written.Balance, recovered.Balance, AccountBalanceEquals);\n        EnsureEqual(\"profile\", written.Profile, recovered.Profile, ProfileStateEquals);\n        EnsureEqual(\"completion status\", written.CompletionStatus, recovered.CompletionStatus, static (left, right) => left == right);\n        EnsureEqual(\"receipt\", written.Receipt, recovered.Receipt, static (left, right) => left == right);\n    }\n\n    private static void EnsureEqual<T>(string name, IReadOnlyList<T> written, IReadOnlyList<T> recovered, Func<T, T, bool> equals)\n    {\n        if (written.Count != recovered.Count)\n        {\n            throw new InvalidOperationException($\"Recovered {name} count does not match. Written: {written.Count}, recovered: {recovered.Count}.\");\n        }\n\n        for (var i = 0; i < written.Count; i++)\n        {\n            if (!equals(written[i], recovered[i]))\n            {\n                throw new InvalidOperationException($\"Recovered {name} item {i} does not match. Written: {Serialize(written[i])}, recovered: {Serialize(recovered[i])}.\");\n            }\n        }\n    }\n\n    private static void EnsureEqual<T>(string name, T written, T recovered, Func<T, T, bool> equals)\n    {\n        if (!equals(written, recovered))\n        {\n            throw new InvalidOperationException($\"Recovered {name} does not match. Written: {Serialize(written)}, recovered: {Serialize(recovered)}.\");\n        }\n    }\n\n    private static bool InventoryEntryEquals(InventoryEntry left, InventoryEntry right)\n        => string.Equals(left.Key, right.Key, StringComparison.Ordinal)\n            && InventoryItemEquals(left.Value, right.Value);\n\n    private static bool InventoryItemEquals(InventoryItem left, InventoryItem right)\n        => string.Equals(left.Sku, right.Sku, StringComparison.Ordinal)","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/samples/JournalingAzureBlobJson/JournalingAzureBlobJson/Program.cs#L113-L149","documentation":"An InvalidOperationException thrown by EnsureEqual's list loop during recovery validation when the recovered collection has the correct count but an individual element differs from what was written. The message includes the collection name, the item index, and the serialized forms of both the written and recovered values for precise comparison.","triggerScenarios":"After deactivation+recovery, for some index i, equals(written[i], recovered[i]) returns false for one of the collection comparators (InventoryEntryEquals, JournalEventEquals, WorkItemEquals, or the tags ordinal string compare). Counts matched, so this is a per-field value divergence.","commonSituations":"A field is not serialized/deserialized correctly (missing [Id(n)] attribute, missing source-gen for a type), a value-type equality bug (e.g., DateTimeOffset precision, decimal culture), or a journal codec dropping a sub-field. Schema drift between write and recover.","solutions":["Compare the serialized written vs recovered values in the error message to identify the differing field.","Verify every field has a serialization attribute ([Id]/source-gen) and the type is in the JsonSerializerContext.","Check comparators for precision/culture issues (DateTimeOffset rounding, decimal, ordinal vs culture string compares)."],"exampleFix":"// before (field without stable serialization/compare)\npublic sealed record JournalEvent(string EventId, DateTimeOffset Timestamp, ...);\n\n// after (explicit Ids + ordinal-aware compare)\n[GenerateSerializer]\npublic sealed record JournalEvent(\n    [property: Id(0)] string EventId,\n    [property: Id(1)] DateTimeOffset Timestamp,\n    ...);\n// comparator uses DateTimeOffset == and StringComparison.Ordinal","handlingStrategy":"validation","validationCode":"static void RequireEqual<T>(string name, IReadOnlyList<T> written, IReadOnlyList<T> recovered, Func<T,T,bool> eq) {\n    for (int i = 0; i < written.Count; i++)\n        if (!eq(written[i], recovered[i]))\n            throw new InvalidOperationException($\"{name}[{i}] differs: {Serialize(written[i])} vs {Serialize(recovered[i])}\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Annotate every serialized field with [Id(n)] / include the type in the JsonSourceGeneration context.","Use culture-invariant, ordinal comparisons in equality functions (DateTimeOffset, decimal, strings).","Compare the serialized written vs recovered payloads in the message to localize the divergent field."],"tags":["orleans","journaling","durability","test-assertion","serialization","data-integrity"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}