Skip to content

fix(datastore): batch insert of command rows fails on Postgres for derived command types - #250

Open
jordanfelle wants to merge 2 commits into
Chaptarr:developfrom
jordanfelle:fix-insertmany-declared-type-binding
Open

jordanfelle wants to merge 2 commits into
Chaptarr:developfrom
jordanfelle:fix-insertmany-declared-type-binding

Conversation

@jordanfelle

@jordanfelle jordanfelle commented Sep 27, 2026 •

Copy link
Copy Markdown

Fixes #227.

Problem

BasicRepository.InsertMany (batch paths for Postgres and SQLite) adds each value with parameters.Add(name, prop.GetValue(model)), so Dapper resolves the type handler from the value's runtime type. CommandModel.Body has a handler registered for the base type Command only, so any derived command fails with The member p0_Body of type ...RefreshAuthorCommand cannot be used as a parameter value. Single Insert binds by the declared property type and works. The bulk author editor hit this through CommandQueueManager.PushMany (worked around per command in #210).

Fix

Register the same CommandConverter handler for every concrete command type (CommandConverter.ConcreteCommandTypes, plus UnknownCommand), so the lookup by runtime type succeeds. CommandModel.Body is the only column found whose handler is registered for a base type; the other handlers (OsPath, Guid, QualityModel, dictionaries, List<int>, ...) are keyed on the exact runtime types stored. After this, PushMany (#210) could go back to a single InsertMany.

Verification

New SQLite-backed BasicRepositoryInsertManyDeclaredTypeFixture: a batch of derived commands is stored and read back as the right type, stores the same value as single Insert, and null / other handler columns still work. The tests fail without the fix with the exact production error (3,023 core tests pass).

Untested on Postgres (none available here): the failing lookup is Dapper's and is independent of the database; the SQLite batch path shares the same parameter-binding code.

  • Adds every_concrete_command_type_should_have_the_command_handler_registered, a guard that fails if a future command type is not covered by the reflection-based registration (54 concrete types plus UnknownCommand today; the same pattern TableMapping already uses for IProviderConfig) (3,024 core tests pass).

Dapper resolves a type handler from the runtime type of a parameter value. The batch insert path of BasicRepository.InsertMany binds parameters by prop.GetValue(model), so a CommandModel.Body holding a derived command (RefreshAuthorCommand, ...) found no handler (one was registered only for the base type Command) and failed with 'The member p0_Body of type ... cannot be used as a parameter value'. Single Insert binds by the declared type and worked. Register the same handler for each concrete command type. Adds a SQLite-backed InsertMany fixture.
jordanfelle added a commit to jordanfelle/chaptarr that referenced this pull request Sep 27, 2026
jordanfelle added a commit to jordanfelle/chaptarr that referenced this pull request Sep 27, 2026
…egistered

The registration discovers command types by reflection over the core assembly and a Command name suffix; a command added elsewhere or named differently would silently fail its batch insert on Postgres again. Fails if any concrete command type in the core assembly lacks the handler.
jordanfelle added a commit to jordanfelle/chaptarr that referenced this pull request Sep 27, 2026
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.

[BUG] BasicRepository.InsertMany binds parameters by runtime type, so polymorphic handler-typed columns fail on Postgres

1 participant