Skip to content

Announcement/Input - DapperAOT progress and Dapper API / feature update #1909

@mgravell

Description

@mgravell

There's a lot of preamble and back-story here. If you're short of time, scroll down to "Call to action".


You would be forgiven for thinking

The devs aren't iterating Dapper any more, is it dead?

I'm happy to say that the answer is an emphatic "no, it isn't dead", and "yes, we are still here" - and while ADO.NET isn't quite as central to our current roles at Microsoft as it was when we worked at Stack Overflow, it underpins a lot of the teams that use the things that we (@mgravell and @NickCraver) work on.

So: I've been a little hesitant to invest heavily in extending Dapper in its current form, for a few reasons, mostly related to metaprogramming. A lot of my thoughts are captured here. The key impact of this is:.

  • hard to debug at runtime
  • lots of runtime work, both initially (emitting strategies) and each time (strategy cache lookups, metadata verification, etc)
  • hard to develop, with few people able to contribute
  • in particular, hard to extend async code, since ref-emit and async are not good friends
  • expensive to maintain - a "small" change can have huge impact to the ref-emit code, and cause instability
  • absolutely categorically does not work with AOT or other ref-emit constrained runtimes

I've wanted to improve that state. Initially I spent a while looking at a "reimagining of Dapper" that used the extended partial methods support in C# 9? 10? and "generators", so that an analyser understood the intent of your code (which looked very different to Dapper today), and wrote the missing pieces. It worked, but it was inelegant, and frankly "just use this completely different API" is a terrible story re adoption.

But last week, I had an epiphany. I learned about "interceptors", which are hopefully going to be released in net8. Interceptors are a new C# vNext feature that allow additional code (typically via a generator) to say "that method call there? yeah, just ignore where that's going - come here instead" - effectively, it allows code to retarget what arbitrary method calls do.

So how is this relevant to Dapper? Well, if an analyzer can find your connection.QueryAsync<Customer>(...) call, it can spit out an interceptor for that specific call (or multiple call-sites if it wants), that is a method that achieves the same result (probably forwarding the same arguments, unless it wants to optimize), but using an API that allows generated code to deal with all the parameter packing and row parsing. To make a long story short ("too late!"), it makes your existing Dapper code work without requiring any ref-emit at runtime, with zero ongoing strategy cache checks, in a fully AOT way.

I have a proof-of-concept that has this working, with regular Dapper code that is made AOT-friendly just by adding a build package. It works with all the simple scalar, non-query, and typed query methods in both their synchronous and asynchronous forms - showing that the idea is sound. With tests that make it trivial to see what code is generated for a given input. There's also consideration of opt-out, and scenarios where the query is dynamic and being passed down (for wrapper data access layers) - although this area is not yet fully developed. Oh, and did I mention that the new implementation can be used in a mockable way (†)?

Call to action

Sounds great? I think so, at least. So, my thinking is - and this is where I want input; does the following sound reasonable?

  • this AOT approach should be the future of Dapper
  • we should continue to have the ref-emit internals and existing implementation, but I do not plan any new feature development in the ref-emit code
  • the AOT mode should usually not require any code-changes, but a new API will also be available (in particular for use in wrapper data-access layers); your existing code should just work
  • things that currently require additional runtime configuration may need some new annotation-based (or similar) approach; such features may not be available initially (emphasis: AOT mode is configurable at any granularity, so: you can opt-out if needed for some scenarios)
  • we should add the missing IAsyncEnumerable<T> support in the main library (this is already supported in the AOT demo) - update: done
  • we should prioritize (in the net8 time-frame) getting the AOT mode deployed as an optional opt-in feature
  • initially there may be some feature variance between the two, which we will need to work on
  • new features will be added via the AOT mode only, where the impact is easy to understand and validate (by virtue of being able to read the generated code)
  • the analyzer, when enabled, should be given the optional ability to report on times it wasn't able to rewrite a method, explaining why
  • no non-beta previews until "interceptors" ships
  • the AOT mode should target the full breadth of TFMs when possible, but it is acknowledged that using "interceptors" is dependent on using up-to-date compilers and build tools
  • but without them, your existing code should still work unchanged in the ref-emit style
  • this work basically becomes the vNext of Dapper, probably with a "major" denoting the step change (although no breaking API shape); as above, any vNext features would be based on the new code approach

That's it; that's the update and the "does this sound reasonable?". Now your turn; your thoughts please!


† = using the mockable mode requires using slightly different code - which is still unmistakably Dapper - and requires an object allocation (think "box") which is not needed when using the non-mockable mode

Metadata

Metadata

Assignees

Labels

announcementarea:apiAPI Additions or Changesv3.0Changes awaiting the next breaking release

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions