{"record":{"id":"b3fa4b2ae64a0f67","repo":"elsa-workflows/elsa-core","slug":"document-documentid-in-storage-unit-storageunit-expected","errorCode":null,"errorMessage":"Document '{documentId}' in storage unit '{storageUnit}' expected version '{expectedVersion?.ToString() ?? \"<none>\"}' but actual version was '{actualVersion?.ToString() ?? \"<none>\"}'.","messagePattern":"Document '(.+?)' in storage unit '(.+?)' expected version '(.+?)' but actual version was '(.+?)'\\.","errorType":"exception","errorClass":"DocumentStoreConcurrencyException","httpStatus":409,"severity":"error","filePath":"src/modules/Elsa.Persistence.VNext.MongoDb/MongoDbDocumentStore.cs","lineNumber":170,"sourceCode":"\n        var missingFields = collection.Indexes\n            .SelectMany(x => x.Fields)\n            .Distinct(StringComparer.Ordinal)\n            .Where(field => !request.IndexValues.ContainsKey(field))\n            .Order(StringComparer.Ordinal)\n            .ToList();\n\n        if (missingFields.Count > 0)\n            throw new DocumentStoreValidationException($\"Storage unit '{collection.Name}' requires index values for fields '{string.Join(\", \", missingFields)}'.\");\n    }\n\n    private static void ValidateExpectedVersion(string storageUnit, string documentId, long? expectedVersion, long? actualVersion)\n    {\n        if (expectedVersion is null)\n            return;\n\n        if (expectedVersion != (actualVersion ?? 0))\n            throw new DocumentStoreConcurrencyException(storageUnit, documentId, expectedVersion, actualVersion);\n    }\n\n    private static async Task<StoredDocument?> LoadDocumentAsync(IMongoCollection<BsonDocument> collection, string storageUnit, string id, CancellationToken cancellationToken)\n    {\n        var document = await collection.Find(Builders<BsonDocument>.Filter.Eq(\"_id\", id)).FirstOrDefaultAsync(cancellationToken);\n        return document is null ? null : ReadDocument(document, storageUnit);\n    }\n\n    private static FilterDefinition<BsonDocument> CreateSaveFilter(SaveDocumentRequest request)\n    {\n        var filter = Builders<BsonDocument>.Filter;\n\n        if (request.ExpectedVersion is null)\n            return filter.Eq(\"_id\", request.Id);\n\n        return filter.And(filter.Eq(\"_id\", request.Id), filter.Eq(\"Version\", request.ExpectedVersion.Value));\n    }\n","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Persistence.VNext.MongoDb/MongoDbDocumentStore.cs#L152-L188","documentation":"ValidateExpectedVersion implements optimistic concurrency: when a request carries an expectedVersion, the store compares it against the document's current version (treating a missing document as version 0) and throws DocumentStoreConcurrencyException on mismatch. This prevents lost updates when concurrent writers modify the same document.","triggerScenarios":"Calling SaveAsync or DeleteAsync with a non-null expectedVersion that does not match the stored document's Version (or the document does not exist while expectedVersion != 0).","commonSituations":"Two processes/workflow instances writing the same document concurrently; a client retrying an operation after another writer already incremented the version; stale cached version values after a delete+recreate.","solutions":["Re-read the document to get its current Version, apply your changes, and retry with the fresh expectedVersion.","Catch DocumentStoreConcurrencyException and implement a retry-with-reload loop for idempotent writes.","If you don't need concurrency control, pass expectedVersion: null, but understand you lose optimistic-locking protection."],"exampleFix":"// before\nawait store.SaveAsync(collection, request with { ExpectedVersion = 3 }, ct); // actual is 4\n\n// after\nvar existing = await store.LoadAsync(collection, id, ct);\nawait store.SaveAsync(collection, request with { ExpectedVersion = existing?.Version }, ct);","handlingStrategy":"retry","validationCode":"var current = await store.LoadAsync(collection, id, ct);\nif (expectedVersion.HasValue && expectedVersion.Value != (current?.Version ?? 0))\n    throw new InvalidOperationException(\"Version conflict detected before save; reload first.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    await store.SaveAsync(collection, request, ct);\n}\ncatch (DocumentStoreConcurrencyException ex)\n{\n    logger.LogWarning(ex, \"Concurrency conflict on {Unit}/{Id}\", ex.StorageUnit, ex.DocumentId);\n    // reload and retry\n}","preventionTips":["Always pass the version obtained from your last read as expectedVersion; never hard-code versions.","Keep read-modify-write windows short to reduce conflict probability.","Implement a bounded retry (reload, re-apply, re-save) around concurrency-sensitive writes."],"tags":["mongodb","concurrency","optimistic-locking","persistence"],"backgroundTag":"invalid-state-transition","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}