{"record":{"id":"e79af93b3fc09b42","repo":"elsa-workflows/elsa-core","slug":"unable-to-save-data","errorCode":null,"errorMessage":"Unable to save data","messagePattern":"Unable to save data","errorType":"exception","errorClass":"DataProcessingException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Persistence.EFCore.PostgreSql/Handlers/DbExceptionTransformer.cs","lineNumber":20,"sourceCode":"using JetBrains.Annotations;\nusing Npgsql;\n\nnamespace Elsa.Persistence.EFCore.PostgreSql.Handlers;\n\n/// <summary>\n/// Transforms database exceptions encountered when using a postgreSQL database into more generic exceptions.\n/// </summary>\n[UsedImplicitly]\npublic class DbExceptionTransformer : IDbExceptionHandler\n{\n    public Task HandleAsync(DbUpdateExceptionContext context)\n    {\n        var exception = context.Exception;\n        \n        if (exception.InnerException is PostgresException { SqlState: \"23505\" })\n            throw new UniqueKeyConstraintViolationException(\"Unable to save data\", exception);\n\n        throw new DataProcessingException(\"Unable to save data\", exception);\n    }\n}","sourceCodeStart":2,"sourceCodeEnd":22,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Persistence.EFCore.PostgreSql/Handlers/DbExceptionTransformer.cs#L2-L22","documentation":"Same handler as the 23505 branch: any DbUpdateException whose inner exception is NOT a unique violation (or has no PostgresException inner) is rethrown as a generic DataProcessingException('Unable to save data', exception). It wraps all non-unique-constraint PostgreSQL save failures so callers get a consistent Elsa persistence exception type while retaining the original as InnerException.","triggerScenarios":"Calling a repository/SaveChanges operation against PostgreSQL where the update fails for reasons other than a 23505 unique violation: foreign key violations (23503), not-null violations (23502), deadlocks, serialization failures, connectivity loss during commit.","commonSituations":"Deleting a parent record still referenced by children (FK violation); missing required column values after a model change without a migration; transient network/database outages; concurrent modification conflicts.","solutions":["Inspect ex.InnerException (PostgresException) for SqlState and Message to identify the real cause.","Fix the underlying data/model issue (add missing migration, fix FK references, supply required fields).","Retry transient failures (connection resets, serialization failures) with a retry policy.","Catch DataProcessingException around persistence calls and log the inner exception details."],"exampleFix":"// before\nawait repository.SaveAsync(entity);\n// after\ntry { await repository.SaveAsync(entity); }\ncatch (DataProcessingException ex) when (ex.InnerException is PostgresException pg && pg.SqlState == \"23503\")\n{\n    // handle FK violation\n}","handlingStrategy":"try-catch","validationCode":"// Validate required fields and FK references before saving\nif (string.IsNullOrWhiteSpace(entity.RequiredField))\n    throw new InvalidOperationException(\"RequiredField must be set before saving\");\nvar fkOk = await parents.AnyAsync(p => p.Id == entity.ParentId, ct);\nif (!fkOk) throw new InvalidOperationException(\"ParentId references a missing parent\");","typeGuard":"static bool IsTransient(DataProcessingException ex) =>\n    ex.InnerException is PostgresException { SqlState: \"40001\" or \"40P01\" or \"08006\" or \"08003\" };","tryCatchPattern":"try\n{\n    await repository.SaveAsync(entity, ct);\n}\ncatch (DataProcessingException ex)\n{\n    var pg = ex.InnerException as PostgresException;\n    logger.LogError(pg, \"Save failed with SqlState {SqlState}\", pg?.SqlState);\n    if (IsTransient(ex)) await RetrySaveAsync(entity, ct); // retry transient failures\n    else throw;\n}","preventionTips":["Always log InnerException (PostgresException) — the wrapper message 'Unable to save data' hides the cause.","Run EF migrations before deploying model changes to avoid not-null/column mismatches.","Apply a retry policy (e.g. Polly) for transient PostgreSQL errors (connection drops, serialization failures).","Enforce FK integrity in code (validate references) before deleting parents."],"tags":["postgresql","efcore","persistence","save-failure"],"backgroundTag":"database-write-failed","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}