Skip to content

Fix InvalidCastException and PostgreSQL transaction abort in HTTP sync#12

Open
davidortinau wants to merge 1 commit intoadospace:mainfrom
davidortinau:fix/postgres-http-sync-cast-and-transaction
Open

Fix InvalidCastException and PostgreSQL transaction abort in HTTP sync#12
davidortinau wants to merge 1 commit intoadospace:mainfrom
davidortinau:fix/postgres-http-sync-cast-and-transaction

Conversation

@davidortinau
Copy link

Problem

When using the HTTP sync protocol with a PostgreSQL server and SQLite mobile clients (mixed-provider sync), two bugs cause server-side failures:

Bug 1: InvalidCastException in CompleteApplyBulkChangesAsync

System.InvalidCastException: Unable to cast object of type 'System.String' to type 'System.Text.Json.JsonElement'
   at CoreSync.Http.Server.SyncAgentController.CompleteApplyBulkChangesAsync(Guid sessionId)

Root cause: CompleteApplyBulkChangesAsync (line 174) hard-casts itemValueEntry.Value.Value to JsonElement, but values may already be native .NET types. This happens when:

  • MessagePack deserialization returns native types instead of JsonElement
  • Newer versions of System.Text.Json (e.g., .NET 10) may deserialize object? properties differently

Fix: Use pattern matching (is JsonElement) instead of a hard cast. If the value is already a native .NET type, skip the conversion.

Bug 2: PostgreSQL transaction abort (25P02)

Npgsql.PostgresException: 25P02: current transaction is aborted, commands ignored until end of transaction block

Root cause: In PostgreSQLSyncProvider.ApplyChangesAsync, when a DML statement fails (e.g., FK constraint violation), the exception is caught and logged, but the transaction continues. Unlike SQLite, PostgreSQL enters an "aborted" state after any error within a transaction — all subsequent commands are rejected with error 25P02.

Fix: Wrap each DML operation in a SAVEPOINT. On failure, rollback to the savepoint so the transaction remains usable for subsequent items. This matches the SQLite provider's behavior of skipping failed items gracefully.

Reproduction

  1. Set up a PostgreSQL server with CoreSync HTTP endpoints
  2. Set up a SQLite mobile client (e.g., .NET MAUI app)
  3. Populate the mobile client with data
  4. Trigger sync from mobile → server

Expected: Sync completes successfully
Actual: Server crashes with InvalidCastException, and any SQL errors cascade into 25P02

Changes

File Change
SyncAgentController.cs Pattern match is JsonElement instead of hard cast in CompleteApplyBulkChangesAsync
SyncProviderHttpClient.cs Same fix in client-side ConvertJsonValueToNetObject for robustness
PostgreSQLSyncProvider.cs Wrap DML in SAVEPOINT/RELEASE/ROLLBACK TO SAVEPOINT for error recovery

Testing

Verified end-to-end with:

  • Server: .NET 10 API with PostgreSQL (via Aspire), CoreSync.Http.Server + CoreSync.PostgreSQL
  • Client: .NET MAUI Mac Catalyst app with SQLite, CoreSync.Http.Client + CoreSync.Sqlite
  • Sync of ~12K rows completes successfully with these fixes applied

Bug 1: SyncAgentController.CompleteApplyBulkChangesAsync hard-casts values
to JsonElement, which fails when values are already native .NET types (e.g.
from MessagePack deserialization or newer System.Text.Json versions that
may resolve object? differently). Fixed by using pattern matching (is
JsonElement) and passing through native types without conversion.

Applied the same fix to the client-side ConvertJsonValueToNetObject for
robustness.

Bug 2: PostgreSQLSyncProvider.ApplyChangesAsync catches DML exceptions but
continues the transaction. Unlike SQLite, PostgreSQL enters an 'aborted'
state (25P02) after any error within a transaction, causing all subsequent
commands to fail. Fixed by wrapping each DML statement in a SAVEPOINT so
failures can be rolled back without aborting the entire transaction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
davidortinau added a commit to davidortinau/SentenceStudio that referenced this pull request Mar 20, 2026
…ment

Includes fixes for InvalidCastException and PostgreSQL transaction abort
in HTTP sync (see adospace/CoreSync#12). Using local packages until
upstream PR is merged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@adospace
Copy link
Owner

Hi, sorry, but I think this PR has some problems.
Can you give some more context on the first one? When the binary formatter is selected on the HTTPClient, the controller method called is not the one that parses the JSON payload, but the other that deals with a binary payload that is parsed with MessagePack. So it's correct that it throws an invalid cast, as it expects JSON data.

Regarding the second fix, I'm not convinced that PostgreSQL should behave like SQLite.
PostgreSQL, like SQL Server, is generally used for the server/central database, and shouldn't be allowed to just skip records because they do not have a valid foreign key.
I've relaxed this rule for SQLite (but I'm planning to opt out with a setting) because it's generally used as a mobile/client database: often, a foreign key constraint validation failing is caused by an old version of the app with an old schema trying to sync with the server. In that scenario, in my experience, it's better to have something written on the DB than nothing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants