Skip to content

Resolve value-converter column DB type from the converter provider type - #5645

Merged
MaceWindu merged 1 commit into
masterfrom
feature/value-converter-column-dbtype
Jun 26, 2026
Merged

MaceWindu merged 1 commit into
masterfrom
feature/value-converter-column-dbtype

Conversation

@MaceWindu

Copy link
Copy Markdown
Contributor

Summary

A column with a ValueConverter but no explicit DataType resolved its DB type from the
member (model) type, which usually has no DB type of its own — so the column fell back to
Undefined and dropped the element's facets (precision/scale/length).

Most visibly, an F# decimal option (provider type Nullable<decimal>) collapsed to a bare
Decimal and silently truncated scale on strict providers instead of the provider's
decimal(18,10); string option likewise ignored the provider's preferred string type.

Fix

In ColumnDescriptor.GetDbDataType, when no explicit DataType is set, resolve the DB type from
the converter's provider type (ToProviderExpression result type) against the descriptor's
active, provider-inclusive MappingSchema — falling back to the member type when there is no
converter.

Impact

  • Affects every value-converter column with an undefined DataType (enums, custom scalar
    converters, F# option columns). Such columns now pick up the provider-faithful element type and
    its facets. Baselines will move accordingly.
  • No public-API surface change.

Context

Surfaced reviewing #5624 (automatic F# 'T option mapping, fixes #195 / #4646): the metadata
reader can only resolve against MappingSchema.Default, so provider precision was lost. This core
change lets the F# reader drop its explicit DataType and get provider-faithful option columns;
that follow-up rides on #5624's branch once this merges.

🤖 Generated with Claude Code

A column with a ValueConverter but no explicit DataType resolved its DB type
from the member (model) type, which usually has no DB type of its own - so the
column fell back to Undefined and dropped the element's facets. Most visibly an
F# 'decimal option' (provider type Nullable<decimal>) collapsed to a bare
Decimal and silently truncated scale on strict providers, instead of the
provider's decimal(18,10); 'string option' likewise ignored the provider's
preferred string type.

Resolve the DB type from the converter's provider type (ToProviderExpression
result type) against the descriptor's active, provider-inclusive MappingSchema
when no explicit DataType is set, falling back to the member type when there is
no converter.

Add ValueConverterColumnDbTypeTests covering DataType resolution and
precision/scale propagation for a converter column without an explicit DataType.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@MaceWindu MaceWindu added this to the 6.4.0 milestone Jun 21, 2026
@MaceWindu MaceWindu self-assigned this Jun 21, 2026
@MaceWindu

Copy link
Copy Markdown
Contributor Author

/azp run test-all

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MaceWindu MaceWindu moved this from Todo to In Progress in PR Review Queue Jun 22, 2026
@MaceWindu MaceWindu moved this from In Progress to In Review in PR Review Queue Jun 23, 2026
@MaceWindu
MaceWindu marked this pull request as ready for review June 23, 2026 21:45
@MaceWindu
MaceWindu merged commit 50fefae into master Jun 26, 2026
54 of 57 checks passed
@MaceWindu
MaceWindu deleted the feature/value-converter-column-dbtype branch June 26, 2026 06:53
@github-project-automation github-project-automation Bot moved this from In Review to Done in PR Review Queue Jun 26, 2026
@MaceWindu

Copy link
Copy Markdown
Contributor Author

📝 Release-notes draft

🤖 Auto-generated user-facing summary for this PR. Toggle the boxes to control how it ships; the text is regenerated when new commits land (the maintainer confirms every change).

  • Omit from release notes (exclude from both the wiki notes and the GitHub release highlights)
  • Include in the GitHub release highlights (the brief release-page notes)

Full release notes (wiki)

  • Fixed a column that has a ValueConverter but no explicit DataType resolving its database type from the model member type (which usually has none), so it fell back to Undefined and dropped precision/scale/length facets. The DB type is now resolved from the converter's provider type — for example an F# decimal option now maps to the provider's decimal(18,10) instead of collapsing to a bare Decimal and truncating scale. (#5645)

GitHub release highlight (brief)

(none)


Generated from commit 5c4e232.

MaceWindu added a commit that referenced this pull request Jul 3, 2026
…er type (MIN003)

Drop the explicit DataType the F# option metadata reader derived from
MappingSchema.Default. With no explicit DataType, ColumnDescriptor now
resolves the column's DB type from the value converter's provider type
against the connection's provider-inclusive schema (#5645), preserving
provider-faithful facets (decimal precision/scale, string length) that
MappingSchema.Default has no provider context to supply.

Previously a 'decimal option' collapsed to decimal(18,0) and silently
truncated scale on strict providers (SQL Server: Some 12.34m -> 12); it
now maps to the provider's decimal(18,10) and round-trips intact. Kept the
explicit CanBeNull = true (load-bearing for the non-nullable struct voption
case). Add Option_DecimalRoundtrip regression test (verified red->green on
SQL Server 2022) and correct the readme limitation note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MaceWindu added a commit that referenced this pull request Jul 4, 2026
* Fix #195, #4646: automatic F# option type mapping

Add automatic mapping of F# 'T option columns so .UseFSharp() handles them
with no manual MappingSchema config: Some v stores the value, None stores
NULL. Value-typed options (e.g. int option) route through Nullable<'T>,
fixing #4646 where None was stored as 0.

Implemented via a metadata reader supplying, per 'T option member, a
ScalarTypeAttribute (column classification), DataTypeAttribute (DB column
type from the element), and a ValueConverterAttribute (None<->NULL,
Some<->value). No core changes; no public API surface change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix #195, #4646: combine F# option schema as lower-priority fallback

UseFSharp registered the option mapping schema via UseAdditionalMappingSchema,
which adds it at *higher* priority. That schema's embedded default attribute
reader then shadowed users' fluent column metadata: since EntityDescriptor
keeps the first ColumnAttribute per member, an explicit fluent DataType
(e.g. rowversion / Binary) on an attributed entity was dropped in favour of
the plain [Column] attribute. This broke ConcurrencyTests across SqlServer
(rowversion -> VarBinary), Oracle and SqlCe (Binary(16) -> BLOB) once the
test base applies UseFSharp to every context.

Combine the option schema as a lower-priority fallback (CombineSchemas with
the existing schema first) so auto 'T option mapping only fills in members
the user has not mapped, and explicit mappings keep priority.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Document F# option-column DB-type derivation limitation

Auto-mapped 'T option columns derive their DB type from the element type
against MappingSchema.Default, since a metadata reader has no access to the
connection/provider mapping schema. Provider-specific or user-custom DB-type
overrides for the element are therefore not honored (e.g. string option maps
to NVarChar, not a provider's preferred VarChar). Round-trip is unaffected.

Note the limitation in the metadata-reader comment and readme, and point users
at an explicit DataType (attribute/fluent) when a provider-faithful type is
required.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add regression test: UseFSharp option schema must not shadow explicit fluent DataType

UseFSharp() combines the F# option mapping schema as a lower-priority fallback
so its embedded default attribute reader cannot override a user's explicit
column metadata. Add a focused F# test that pins an explicit fluent DataType
(VarChar) on an option member also carrying a plain [<Column>], and asserts the
entity descriptor keeps VarChar. Verified red->green: with the option schema at
higher priority the column resolves to the derived NVarChar instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address review findings: voption support, scalar gate, Nullable<_> guard

Generalize the F# option value converter and metadata reader in response to
review of #195:

- Support F# struct value-options ('T voption / FSharpValueOption<_>) alongside
  reference options. The converter now branches on option-kind (IsValueSome /
  NewValueSome / ValueNone vs reference-null / Some) and on element-kind.
- Guard the Nullable<_> element edge case: a 'Nullable<int> option' previously
  built a Nullable<Nullable<int>> provider type and threw in MakeGenericType.
  The converter now uses an already-Nullable element directly.
- Only auto-map options whose element is a scalar type (IsScalarOption gates
  both metadata-reader overloads), so an option over a complex/entity element is
  no longer force-scalarized.
- Mark auto-mapped option columns CanBeNull: a struct value-option is a
  non-nullable value type, so without it the DDL emitted NOT NULL and rejected
  the "none" case.

Add F# round-trip tests for Nullable<_>-element options and value-options, and
note voption support in the readme.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* F# option mapping: resolve option column DB type via converter provider type (MIN003)

Drop the explicit DataType the F# option metadata reader derived from
MappingSchema.Default. With no explicit DataType, ColumnDescriptor now
resolves the column's DB type from the value converter's provider type
against the connection's provider-inclusive schema (#5645), preserving
provider-faithful facets (decimal precision/scale, string length) that
MappingSchema.Default has no provider context to supply.

Previously a 'decimal option' collapsed to decimal(18,0) and silently
truncated scale on strict providers (SQL Server: Some 12.34m -> 12); it
now maps to the provider's decimal(18,10) and round-trips intact. Kept the
explicit CanBeNull = true (load-bearing for the non-nullable struct voption
case). Add Option_DecimalRoundtrip regression test (verified red->green on
SQL Server 2022) and correct the readme limitation note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add regression test: complex-element option is not auto-scalarized

The F# option metadata reader only auto-maps options whose element is a
scalar type (IsScalarOption gates on MappingSchema.Default.IsScalarType).
Every positive branch had a round-trip test; the negative branch (an option
over a complex/entity element) had none. Add Option_ComplexElementNotScalarized:
a scalar-element option carries the F# value converter while a complex-element
option is left untouched. Verified green on SQLite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Document UseFSharp features as a list; note voption support

Convert the UseFSharp <remarks> to a bullet <list> and mention that
automatic mapping covers F# 'T voption columns in addition to 'T option.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add regression test: option over user/provider-only scalar type (ActiveIssue)

The F# option auto-mapping gate (FSharpOptionSupport.IsScalarOption) consults
MappingSchema.Default, so an option whose element is scalar only in the active
user/provider schema (registered via AddScalarType, e.g. a custom value object
or a provider-native type) is not recognised as a column and gets no None<->NULL
converter. Add Option_CustomScalarElementMapped pinning this; it fails on current
HEAD, so it is [ActiveIssue]-gated until the gate is fixed to consult the active
schema. Verified red->skipped on SQLite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

F# option types are not supported

1 participant