Type handlers: runtime SqlMapper.AddTypeHandler registrations honored end-to-end - #206
Open
mgravell wants to merge 2 commits into
Open
Type handlers: runtime SqlMapper.AddTypeHandler registrations honored end-to-end#206mgravell wants to merge 2 commits into
mgravell wants to merge 2 commits into
Conversation
…announced attributes
SqlMapper.AddTypeHandler registrations now work under interception, in both directions, by delegating to vanilla's own decision procedure at execution time rather than trying to see runtime state at compile time: - writes: a member type the generator does not recognize emits a dispatch through SqlMapper.LookupDbType (public, CS0618-suppressible - the same tier as PackListParameters): handler present -> handler.SetValue (with DBNull for null, never null itself - the TypeHandler<T> interface impl NREs on a raw null); otherwise any returned DbType is applied and the value binds raw as before (demand:false deliberately - modern providers natively handle types vanilla's map does not, DateOnly being the live case until the Dapper re-enable ships). Update-mode mirrors it, so command reuse stays legal (the parameter shape is stable); an expandable member checks the handler *first*, which is the order vanilla applies (a handled collection type must not list-expand - Issue253). - reads: the lib cannot reference Dapper (a consumer may use Dapper or Dapper.StrongName, and a hard reference would split the handler registry between the two), so generated code installs a TypeHandlerBridge from a module initializer, compiled against the consumer's own Dapper; the flexible read path consults it, and a whole-type handler overrides a generated row factory (RowFactory<T>.Resolve), matching vanilla's handler-before-member-binding order. A ModuleInitializerAttribute polyfill is emitted for down-level targets, probe-gated like the interceptor attribute; the whole feature is inert against a Dapper too old to have HasTypeHandler/LookupDbType. - char/char? stay excluded (their StringFixedLength map entry pads the round-trip); object/dynamic stay excluded (runtime-typed values); ParamMember gains TypeOfName, mirroring RowMember's, because typeof on an annotated reference type or dynamic does not compile. This also clears the bare-DataTable TVP shape and the Xml types for free - vanilla registers DataTableHandler and the XML handlers by default, and the dispatch reaches them like any other registration. Dapper test suite: 677 -> 705/793. Design and probed facts in notes/typehandlers-design.md; prior art PRs #117 and #162 (the announced- attribute tier) remain as the static-dispatch optimization, redone on the plain-data model.
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.
The biggest remaining behavioral class. Design and probed facts in
notes/typehandlers-design.md(committed here); the short version is the delegate-to-vanilla doctrine applied to runtime-mutable state — the generator cannot seeAddTypeHandlercalls at compile time, so generated code defers to vanilla's own decision procedure at execution time:SqlMapper.LookupDbType(public, CS0618-suppressible — the same tier asPackListParameters). Handler present →handler.SetValue, passingDBNullfor null (never null itself —TypeHandler<T>'s interface impl NREs on the struct cast; probed, and vanilla coalesces too). Otherwise any returnedDbTypeapplies and the value binds raw exactly as before (demand: falsedeliberately: modern providers natively handle types vanilla's map doesn't — DateOnly, until the Dapper re-enable ships). Expandable members check the handler first — a handled collection type must not list-expand (Issue253), which is the order vanilla applies. Update-mode mirrors the dispatch, so command reuse stays legal (the parameter shape is stable).PackageReference). Generated code installs aTypeHandlerBridgefrom a module initializer, compiled against the consumer's own Dapper; the flexible read path consults it, and a whole-type handler overrides a generated row factory (RowFactory<T>.Resolve), matching vanilla's handler-before-member-binding order. Down-level targets get a probe-gatedModuleInitializerAttributepolyfill; the whole feature is inert against a Dapper too old to haveHasTypeHandler/LookupDbType.char/char?andobject/dynamicstay excluded from dispatch (padding and runtime-typing respectively);ParamMembergainsTypeOfNamemirroringRowMember's, becausetypeofon an annotated reference type (CS8639) ordynamic(CS1962) doesn't compile — found the hard way when the harness build failed silently.Dapper test suite: 677 → 705/793. The entire runtime-handler family clears (Issue136, Issue1959 ×4, Issue253 ×2, Issue461, SO24740733 ×2, Issue149, the
PreferTypeHandlersForEnumstest —LookupDbTypechecks that setting internally, so it came free), plus the bare-DataTableTVP pair and the Xml tests, because vanilla registersDataTableHandlerand the XML handlers by default and the dispatch reaches them like any registration.Deliberately not here (recorded in the design note):
AddTypeMapon recognized scalars (the string→AnsiString tests — honoring it costs a per-parameter lookup on the hottest types; explicit trade to discuss), and theSetTypeMapruntime column-mapping family (the parity table's 🚫 proposal stands). Prior art #117 (samcragg) and #162 (7amou3) are the announced-attribute tier — the right shape for static dispatch, and still worth doing as an optimization on top of this, redone on the plain-data model (#162's symbol-keyed registry predates phase 2).