feat: TypeHandler support - #162
Conversation
| // Example using a module-level attribute | ||
| using Dapper; | ||
|
|
||
| [module: TypeHandler<YourDotNetType, YourCustomTypeHandler>] |
There was a problem hiding this comment.
If I have a query that required certain set of typeHandlers,
And other do not,
Then how to restore them, and then add if and when needed ?
There was a problem hiding this comment.
Hello, this PR does not currently allow for switching or disabling type handlers on the fly.
This is definitely a feature that could be added, though.
|
Any update on this PR or #117? I am debugging custom Dapper.AOT I have registered them via and they are not used in source generation and I just get runtime cast errors when loading data from DB "Invalid cast from 'System.Int64' to 'MyCustomType'." What is the purpose of |
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 PR represents an attempt to resolve issue #159.
The prior approach of the source generator (#117) inadvertently led to the generation of code that instantiated a new
TypeHandlerobject for each parameter or column read operation (new global::CustomClassTypeHandler().Read(...)). This could lead to unnecessary object allocations and a minor performance overhead, especially in high-volume scenarios.This PR introduces a
TypeHandlerInstanceRegistryand modifies the code generation to ensure that:__Handler1,__Handler2).