Dapper.AOT Documentation

repository·main·Indexed 19 days ago

https://github.com/dapperlib/dapperaot

A build-time code generation tool for ADO.NET that optimizes Dapper usage via Ahead-of-Time (AOT) code generation. It uses .NET 8+ interceptors to replace reflection-based Dapper calls with high-performance generated code, improving cold-start performance and compatibility with linkers and trimmers. The project includes Dapper.AOT for full optimization and Dapper.Advisor for usage guidance and analysis. Key features include support for named tuple types, bulk inserts via TypeAccessor, factory methods, and the DbBatch API.

Tokens
24.3K
Snippets
96
Records
120
Agent score
66%

What's inside Dapper.AOT

  1. Overview of Dapper.AOT

    main
    Dapper.AOT is a set of build-time tools designed to optimize Dapper usage. It uses 'interceptors' to weave AOT (Ahead-of-Time) implementations into your existing Dapper code. This process replaces standard Dapper calls—which rely on reflection and ref-emit—with high-performance code that bypasses those mechanisms. Additionally, it provides usage guidance to help improve how Dapper is utilized in your project.
  2. Overview of Dapper.AOT

    main
    Dapper.AOT is a tool designed to simplify working with ADO.NET by generating the necessary code for command preparation, invocation, and result parsing during the build process. It is intended for developers who want to execute SQL directly without the overhead and ceremony of a full ORM (like EF or LLBLGenPro), while avoiding the manual, error-prone boilerplate typically associated with raw ADO.NET.
  3. Handle datepart tokens in SQL functions

    main

    When using T-SQL date functions like DATEADD or DATEPART, the first argument must be a datepart token (e.g., year, month, day).

    Important Constraint: These datepart tokens are special literal values. They cannot be parameterized using standard SQL parameters (e.g., @param) and they cannot be derived from a column value. They must be provided as hardcoded tokens within the SQL string.

    -- Correct usage: token is a literal
    DATEPART(year, GETDATE())
    
    -- Incorrect usage: tokens cannot be parameterized
    -- DATEPART(@datepart, GETDATE())
    
    -- Incorrect usage: tokens cannot be column values
    -- DATEPART(my_column, GETDATE())
  4. Avoid using types in the Dapper.AOT.Internal namespace

    main
    The Dapper.AOT.Internal namespace contains types that are required for the generated AOT (Ahead-of-Time) code to function correctly. However, these types are not intended for direct consumption by application developers. Using them in your user code may lead to breaking changes in future updates. All types in this namespace are marked with the [Obsolete] attribute to discourage usage.
  5. Understand null literal behavior in bitwise/operator expressions

    main

    In dapperaot, operations involving a null literal do not always yield null. While most operations involving a null value (from a parameter or local variable) evaluate to null as expected, there are specific exceptions when using null literals directly in expressions.

    Specifically, the following patterns involving null literals may yield results other than null:

    • null >> 2 (Right shift with null literal)
    • 2 >> null (Right shift with null literal)
    • null & null (Bitwise AND with null literals, though this typically requires at least one operand to be something other than a 'hard' null to behave predictably).

    Note: This behavior applies specifically to null literals. If a variable or parameter contains a null value, the expression will evaluate to null following standard expectations.

    // Exceptions apply to literals:
    var result1 = null >> 2;
    var result2 = 2 >> null;
    var result3 = null & null; // Note: behavior varies if operands are not 'hard' nulls
    
    // Standard behavior applies to null values in variables:
    int? myValue = null;
    var result4 = myValue >> 2; // Evaluates to null
  6. Difference between Dapper.Advisor and Dapper.AOT

    main

    Dapper provides two distinct tools depending on your requirements:

    • Dapper.Advisor: Provides guidance and analysis on your Dapper usage (via analyzers) without changing how the code executes. It works with both C# and VB.
    • Dapper.AOT: Includes everything in Dapper.Advisor but adds build-time code generation and runtime library code to enable AOT (Ahead-of-Time) compatibility. It works with C# only.

    Use Dapper.AOT if you need to run your code in environments that disallow runtime code generation or if you want to improve cold-start performance and compatibility with linkers/trimmers.

  7. Distinguish between scalar variables and table variables in SQL

    main

    When writing SQL, ensure you distinguish between scalar variables (which hold a single typed value) and table variables (which behave like temporary tables).

    Scalar variables cannot be used in clauses that expect a table structure. You cannot use INSERT, UPDATE, DELETE, or SELECT * FROM on a scalar variable. Instead, you must reference the variable directly in the SELECT list to retrieve its value.

    -- Bad: Attempting to treat a scalar variable as a table
    declare @id int = 0;
    select * from @id;
    
    -- Good: Selecting the scalar variable directly
    declare @id int = 0;
    select @id;
  8. How Row Factories materialize results

    main

    A RowFactory is responsible for converting a DbDataReader into your target object (e.g., a Product). It uses a two-step process to ensure high performance:

    1. Tokenize: This runs once per result set. It inspects the columns returned by the database and identifies how to handle each. It generates a set of tokens that represent the mapping between database columns and object properties. For each column, it generates two paths: one for the ideal data type and one for type-flexible coercion.
    2. Read: This runs once per row. It iterates through the columns using the tokens provided by Tokenize to efficiently populate the object's fields or properties using the DbDataReader.
    // Example RowFactory structure
    private sealed class RowFactory0 : global::Dapper.RowFactory<global::UsageLinker.Product>
    {
        public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span<int> tokens, int columnOffset)
        {
            // Identifies column types and assigns tokens
        }
    
        public override global::UsageLinker.Product Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan<int> tokens, int columnOffset, object? state)
        {
            // Uses tokens to populate the Product object
        }
    }
  9. Understand SQL variable scope and declaration behavior

    main

    SQL variables have a scope that extends to "anywhere later in the code." This leads to two important behaviors:

    1. No block-level re-declaration: You cannot re-declare a local variable inside a conditional branch (like an IF...ELSE block) if it would conflict with the outer scope or if you are attempting to create a scoped version of a variable.

    2. Assignment vs. Declaration: While you cannot re-declare a variable inside a branch, you can assign a value to an existing variable declared earlier in the batch from within a branch.

    Invalid: Re-declaring in branches

    if -- some test
    begin
        declare @id int
        -- more code
    end
    else
    begin
        declare @id int
        -- more code
    end

    Valid: Assigning to a previously declared variable

    declare @id int;
    
    if -- some test
    begin
        set @id = 1;
    end
    else
    begin
        set @id = 2;
    end
    
    -- @id is accessible here as well
    select @id;
  10. How Dapper.AOT works via Interceptors

    main
    Dapper.AOT leverages the .NET 8+ build SDK feature called "interceptors". During the build process, interceptors detect specific Dapper method calls (e.g., QuerySingleOrDefault<Customer>(...)) and redirect them to generated methods that have compatible signatures. These replacement methods are generated entirely at build time, allowing for Ahead-of-Time (AOT) compatibility without changing your existing source code.
  11. Correctly using SQL table-variables

    main

    When working with SQL table-variables, do not attempt to treat them as scalar variables. You cannot use SET or SELECT for direct assignment to the variable itself, nor can you SELECT the variable as if it were a single value. Instead, you must treat the table-variable as a data source and use the FROM clause to select specific columns from it.

    Incorrect pattern: Attempting to select the variable directly. Correct pattern: Selecting specific columns FROM the table-variable.

    -- Bad: Treating a table-variable like a scalar
    declare @t table (Id int not null);
    insert @t (Id) values (42);
    select @t; 
    
    -- Good: Selecting columns FROM the table-variable
    declare @t table (Id int not null);
    insert @t (Id) values (42);
    select Id from @t;