Resolve value-converter column DB type from the converter provider type - #5645
Merged
Merged
Conversation
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>
Contributor
Author
|
/azp run test-all |
|
Azure Pipelines successfully started running 1 pipeline(s). |
MaceWindu
marked this pull request as ready for review
June 23, 2026 21:45
MaceWindu
requested review from
Shane32,
igor-tkachev,
jods4,
sdanyliv and
viceroypenguin
as code owners
June 23, 2026 21:45
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).
Full release notes (wiki)
GitHub release highlight (brief)(none) Generated from commit |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A column with a
ValueConverterbut no explicitDataTyperesolved its DB type from themember (model) type, which usually has no DB type of its own — so the column fell back to
Undefinedand dropped the element's facets (precision/scale/length).Most visibly, an F#
decimal option(provider typeNullable<decimal>) collapsed to a bareDecimaland silently truncated scale on strict providers instead of the provider'sdecimal(18,10);string optionlikewise ignored the provider's preferred string type.Fix
In
ColumnDescriptor.GetDbDataType, when no explicitDataTypeis set, resolve the DB type fromthe converter's provider type (
ToProviderExpressionresult type) against the descriptor'sactive, provider-inclusive
MappingSchema— falling back to the member type when there is noconverter.
Impact
DataType(enums, custom scalarconverters, F# option columns). Such columns now pick up the provider-faithful element type and
its facets. Baselines will move accordingly.
Context
Surfaced reviewing #5624 (automatic F#
'T optionmapping, fixes #195 / #4646): the metadatareader can only resolve against
MappingSchema.Default, so provider precision was lost. This corechange lets the F# reader drop its explicit
DataTypeand get provider-faithful option columns;that follow-up rides on #5624's branch once this merges.
🤖 Generated with Claude Code