We’re going to assume that you already have existing C# or VB code that uses Dapper
. If you’re using another .NET language: sorry,
Dapper.AOT
doesn’t have anything useful for you, but I hope you still enjoy vanilla Dapper
!
Dapper.AOT offers two new tools:
Dapper.Advisor
which offers guidance on your Dapper usage (but does not change how anything works); this works with VB and C#Dapper.AOT
which includes everything from Dapper.Advisor
but which also includes build-time code-generation
and some runtime library code that allows your Dapper code to work in an AOT (see below) way; this works with C# onlyAOT is “ahead of time”. Libraries like Dapper
use a lot of complex reflection at runtime, either directly via the reflection API, or by emitting IL at runtime based on the types that get discovered.
This is very flexible, but has disadvantages:
Dapper.AOT
solves all of these problems as a radical overhaul to how Dapper
works.
Let’s imagine that we have a basic project that uses Dapper
; our csproj might contain:
<ItemGroup>
<!-- ... etc ... -->
<PackageReference Include="Dapper" />
<PackageReference Include="Microsoft.Data.SqlClient" />
</ItemGroup>
(your project file might also have Version
attributes if you’re not using Central Package Management, but it works the same)
and we might have Dapper
code like:
public class Product
{
public static Product GetProduct(SqlConnection connection, int productId) => connection.QueryFirst<Product>(
"select * from Production.Product where ProductId=@productId", new { productId });
public int ProductID { get; set; }
public string Name { get; set; }
public string ProductNumber { get; set; }
// etc
This is using vanilla Dapper
, and we want to enable Dapper.AOT
. To do that, the first thing we need is the .NET 8 build SDK (or later). We do not need to target .NET 8 - we can still target
any framework (including .NET Framework, .NET Standard, .NET Core, or modern .NET), but the magic requires new build features. You can check what build SDK version you are using at the console:
> dotnet --info
.NET SDK:
Version: 8.0.100
(snip lots more here)
If needed, update your SDK from here.
Next, we simply add the Dapper.AOT
nuget package - via the IDE, command-line, or by editing the project file directly:
> dotnet add package Dapper.AOT
or
<ItemGroup>
<!-- ... etc ... -->
<PackageReference Include="Dapper.AOT" />
</ItemGroup>
This installs Dapper.AOT
. To check this, if we build, we see:
> dotnet build
MSBuild version 17.8.3+195e7f5a3 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
C:\Code\DapperAOT\test\UsageLinker\Product.cs(11,99): warning DAP005: 2 candidate Dapper methods detected, but none have Dapper.AOT enabled (https://aot.dapperlib.d
ev/rules/DAP005) [C:\Code\DapperAOT\test\UsageLinker\UsageLinker.csproj]
UsageLinker -> C:\Code\DapperAOT\test\UsageLinker\bin\Debug\net8.0\win-x64\UsageLinker.dll
We don’t want to surprise people by changing how their code works just by installing an additional package, so AOT isn’t enabled by default. As described in the link,
we do this by using [DapperAot]
. Since we’re happy to enable AOT everywhere in our project, we can use (in any C# file):
[module:DapperAot]
But if we prefer, we can annotate individual types or methods with [DapperAot]
(or [DapperAot(false)]
) to turn AOT usage on (or off) at any level.
Now if we build, we get a new error:
> dotnet build
MSBuild version 17.8.3+195e7f5a3 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
C:\Code\DapperAOT\test\UsageLinker\Dapper.AOT.Analyzers\Dapper.CodeAnalysis.DapperInterceptorGenerator\UsageLinker.generated.cs(6,10): error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add '
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Dapper.AOT</InterceptorsPreviewNamespaces>' to your project. [C:\Code\DapperAOT\test\UsageLinker\UsageLinker.csproj]
C:\Code\DapperAOT\test\UsageLinker\Dapper.AOT.Analyzers\Dapper.CodeAnalysis.DapperInterceptorGenerator\UsageLinker.generated.cs(20,10): error CS9137: The 'interceptors' experimental feature is not enabled in this namespace. Add
'<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Dapper.AOT</InterceptorsPreviewNamespaces>' to your project. [C:\Code\DapperAOT\test\UsageLinker\UsageLinker.csproj]
These messages are coming from the build SDK (not Dapper.AOT
). The compiler folks also don’t like surprises, so you need to opt in to the “interceptors” feature that Dapper.AOT
is using. As the message says,
we do this by tweaking our project file:
<PropertyGroup>
<!-- ... etc ... -->
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);Dapper.AOT</InterceptorsPreviewNamespaces>
</PropertyGroup>
This grants permission for tools to generating “interceptors” in the Dapper.AOT
namespace. If we build… silence:
> dotnet build
MSBuild version 17.8.3+195e7f5a3 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
UsageLinker -> C:\Code\DapperAOT\test\UsageLinker\bin\Debug\net8.0\win-x64\UsageLinker.dll
Build succeeded.
0 Warning(s)
0 Error(s)
That’s.. underwhelming, but: a lot is going on behind the scenes. The fact that you didn’t need to change your code is intentional. Your data-access code is now working with build-time code generation, and should work with AOT deployment.
To get the full benefit of the analyzer tools, make sure that it knows which SQL variant you’re using. There are multiple ways of doing this. For example, if we
change our example to use SqlConnection
:
public static Product GetProduct(SqlConnection connection, int productId) => connection.QueryFirst<Product>(
"select * from Production.Product where ProductId=@productId", new { productId });
and build:
> dotnet build
MSBuild version 17.8.3+195e7f5a3 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
C:\Code\DapperAOT\test\UsageLinker\Product.cs(16,17): warning DAP219: SELECT columns should be specified explicitly (https://aot.dapperlib.dev/rules/DAP219) [C:\Code\DapperAOT\test\UsageLinker\UsageLinker.csproj]
It is telling us off for using select *
! Since it knows we’re using SQL Server and TSQL (for more info, see SQL Syntax).
We can fix that (or suppress it if we prefer):
public static Product GetProduct(SqlConnection connection, int productId) => connection.QueryFirst<Product>(
"select ProductID, Name, ProductNumber from Production.Product where ProductId=@productId", new { productId });
SQL analysis is available in both Dapper.AOT
and Dapper.Advisor
, and works for both C# and VB.
Dapper.AOT
does not support all Dapper
features; not all APIs are supported, and when an API is supported it might have limitations - for example, the generic APIs
like Query<Foo>
should work, but the non-generic API passing typeof(Foo)
is not supported. The underlying implementation is completely separate to Dapper
(and usually
your code doesn’t even need Dapper
once compiled); there may be subtle differences in how some things behave.
In particular, any Dapper configuration (including SqlMapper.Settings
, ITypeHandler
, etc) are not used; in many cases
similar configuration is available via new Dapper.AOT
markers. Please ask if you get stuck!
PLEASE TEST YOUR CODE CAREFULLY
If something looks wrong, missing, or would benefit from more usage guidance let us know!