Skip to content

feat(mapping): MapToSqlTableValuedParameter attribute and empty/struct TVP fixes - #2

Draft
dtraft wants to merge 1 commit into
masterfrom
collection-mapping
Draft

feat(mapping): MapToSqlTableValuedParameter attribute and empty/struct TVP fixes#2
dtraft wants to merge 1 commit into
masterfrom
collection-mapping

Conversation

@dtraft

@dtraft dtraft commented Sep 8, 2026

Copy link
Copy Markdown

Posted by: Claude Code (Claude Fable 5.1) | 2026-09-08

TL;DR

Adds MapToSqlTableValuedParameterAttribute so a collection property maps to a SQL Server table-valued parameter on write (and, with the shared mapper change, to an additional result set on read), and fixes two TVP edge cases in AddSqlTableValuedParameter.

Changes

  • SqlMapAttributes.cs: new MapToSqlTableValuedParameterAttribute(parameterName, typeName) : CollectionMapAttributeBase. The emitted expression calls AddSqlTableValuedParameter<TElement> and sets SqlParameter.TypeName. Value-type collections (ImmutableArray<T>) are explicitly converted to IEnumerable<T> (Expression.Call requires reference-assignability), and a default (uninitialized) ImmutableArray<T> -- which throws on enumeration -- is normalized to Empty.
  • SqlParameterCollectionExtensions.cs: both AddSqlTableValuedParameter overloads send DBNull.Value for an empty collection (an empty IEnumerable<SqlDataRecord> cannot supply TDS metadata and is rejected by the driver; SQL Server treats a null TVP as an empty table). The columnList overload previously had no empty guard at all.
  • Tests (new): test/CollectionMapWriteTests.cs (populated/empty/default collections through both overloads and through CreateInputParameters, for List<T> and ImmutableArray<T>) and test/CollectionMapReadTests.cs (multi-result-set hydration, declaration order, empty vs missing result set, missing record, attribute-less model, ImmutableArray<T> read).

Testing

ArgentSea.Sql.Test: 116 passed, 0 failed (101 pre-existing + 15 new). Exercised live against SQL Server by a downstream Orleans grain whose state carries two TVP-mapped collections.

Dependencies

Depends on argentsea/shared (CollectionMapAttributeBase and the read-side handler). Second of four: shared -> sql -> Orleans -> Orleans.Sql.

Related PRs (merge in this order)

  1. feat(mapping): collection-mapped properties with multi-result-set read handler shared#4
  2. feat(mapping): MapToSqlTableValuedParameter attribute and empty/struct TVP fixes #2
  3. feat(persistence): hydrate CollectionMap grain-state properties from extra result sets Orleans#5
  4. fix(persistence): resolve named OrleansDbPersistenceOptions in the Db storage factory Orleans.Sql#3

…t TVP fixes

> Claude Code (Claude Fable 5.1)

Adds MapToSqlTableValuedParameterAttribute mapping a collection property to a table-valued parameter, converting value-type collections such as ImmutableArray<T> to IEnumerable<T> explicitly and normalizing a default ImmutableArray<T> to Empty. Both AddSqlTableValuedParameter overloads now send DBNull for an empty collection instead of an unusable empty record list. Adds read and write tests for the collection mapping.
@dtraft
dtraft force-pushed the collection-mapping branch from 8e7a622 to 11a6cbe Compare September 8, 2026 19:04
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.

1 participant