Dapper.AOT の実装原理を見てみましょう

Dapper.AOTは、Dapperほど機能が充実しているわけではありませんが、基本的な使用ニーズを満たすことができる、.NETが発表するAOTプロジェクトに最初に応答するORMです。

以下は、実装原理のデモに過ぎません。Dapper.AOTの実装には、具体的なソースコードを参照する必要があります。基本的な実装原理は、ソースジェネレータを使用して、プロジェクト内でDapperのメソッドを使用している場所を見つけ出し、インターセプタを使用して元のメソッドを置き換え、新しいメソッドはすべて書き直され、AOTをサポートしているため、開発者が開発体験を変えることなく、または古い方法をリファクタリングする際に、シンプルでダイレクトになります。

1. DapperAOTGeneratorソースジェネレータプロジェクトファイル。
```xml
Copy code
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>12.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <None Remove="MockDapperGenerator-cs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0-beta1.23525.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.0-2.final" />
  </ItemGroup>
</Project>
DapperAOTGeneratorソースジェネレータ実装
```
```csharp
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace DapperAOTGenerator
{
    [Generator]
    public class MockDapperGenerator : ISourceGenerator
    {
        public void Initialize(GeneratorInitializationContext context)
        {
            // 语法树遍历器を登録し、语法树で目標メソッド呼び出しを探します
            context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
        }

        public void Execute(GeneratorExecutionContext context)
        {
            // SyntaxReceiverインスタンスを取得します
            if (!(context.SyntaxReceiver is SyntaxReceiver syntaxReceiver))
                return;
            // コンパイル時の语法树を取得します
            var compilation = context.Compilation;
            var list = new List<(string FilePath, int Line, int Column)>();
            // 语法树を遍历します
            foreach (var syntaxTree in compilation.SyntaxTrees)
            {
                // 语法树の根节点を取得します
                var root = syntaxTree.GetRoot();
                // 条件に合致するメソッド呼び出し节点を全て取得します
                var methodCalls = syntaxReceiver.MethodCalls;
                foreach (var methodCall in methodCalls)
                {
                    // 呼び出し元のファイルパスを取得します
                    var filePath = syntaxTree.FilePath;
                    try
                    {
                        // 呼び出し元の行番号と列番号を取得します
                        var position = syntaxTree.GetLineSpan(methodCall.Span);
                        var line = position.StartLinePosition.Line + 1;
                        var column = position.StartLinePosition.Character + 1 + methodCall.GetText().ToString().IndexOf("Query");
                        // メソッド呼び出しのシンボル情報を取得します
                        var methodSymbol = compilation.GetSemanticModel(syntaxTree).GetSymbolInfo(methodCall).Symbol as IMethodSymbol;

                        if (methodSymbol != null && methodSymbol.IsExtensionMethod && methodSymbol.ReducedFrom != null)
                        {
                            // 呼び出し元の型を取得します
                            var callingType = methodSymbol.ReducedFrom.ReceiverType;

                            if (callingType != null)
                            {
                                // Dapperの拡張メソッドであるかどうかを判断します
                                if (callingType.Name == "SqlMapper")
                                {

                                    if (!filePath.Contains("/obj/") && !filePath.Contains("\\obj\\"))
                                    {
                                        list.Add((filePath, line, column));
                                    }
                                }
                            }
                        }
                    }
                    catch { }
                }
            }
            var sourse = BuildSourse(list);
            context.AddSource("DapperAOTAPITest.g.cs", sourse);
        }

        string BuildSourse(IEnumerable<(string FilePath, int Line, int Column)> lines)
        {
            var codes = new StringBuilder();
            foreach (var line in lines)
            {
                codes.AppendLine($"[InterceptsLocation(@\"{line.FilePath}\", {line.Line}, {line.Column})]");
            }
            var source = $$"""
                         using System;
                         using System.Data;
                         using System.Runtime.CompilerServices;
                       
                         namespace DapperAOTAPITest.Interceptor
                         {
                            public static class DapperInterceptor
                            {                            
                               {{codes.ToString().Trim('\r', '\n')}}                             
                               public static IEnumerable<T> InterceptorQuery<T>(this IDbConnection cnn,string sql,object? param=null, IDbTransaction? transaction=null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
                               {
                                   var message=$"これはQueryのインターセプタです {sql}";
                                   Console.WriteLine(message);
                                   throw new Exception(message);
                               }
                            }
                         }
                         """;
            return source;
        }

        // SyntaxReceiverは、メソッド呼び出しの情報を収集するために使用されます
        class SyntaxReceiver : ISyntaxReceiver
        {
            public List<InvocationExpressionSyntax> MethodCalls { get; } = new List<InvocationExpressionSyntax>();

            public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
            {
                // ここにあなたの语法节点マッチングロジックを追加して、目標メソッド呼び出しの情報を収集します
                if (syntaxNode is InvocationExpressionSyntax invocationSyntax &&
                    invocationSyntax.Expression is MemberAccessExpressionSyntax memberAccessSyntax &&
                    memberAccessSyntax.Name.Identifier.ValueText == "Query")
                {
                    MethodCalls.Add(invocationSyntax);
                }
            }        
        }      
    }
}
```
2. テストプロジェクトDapperAOTAPITestプロジェクトファイル
```xml
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
    <PublishAot>true</PublishAot>
  </PropertyGroup>

  <PropertyGroup>
    <InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);DapperAOTAPITest.Interceptor</InterceptorsPreviewNamespaces>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Dapper" Version="2.1.24" />
    <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0-preview4.23342.2" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\DapperAOTGenerator\DapperAOTGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  </ItemGroup>
</Project>
```
拦截器使用特性InterceptsLocationAttribute.cs
```csharp
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute
    {
    }
}
```
Dapperを使用するクラスTodoRespository.cs
```csharp
using Dapper;
using Microsoft.Data.SqlClient;

namespace DapperAOTAPITest.Respository
{
    public class TodoRespository : ITodoRespository
    {
        public IEnumerable<T> Query<T>()
        {
            using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Test;Integrated Security=True"))
            {
                return conn.Query<T>("select * from Todo ");
            }
        }
    }
}
```
実行結果：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538010/83fd7a0f-1420-25b7-3949-f70cafa4e43a.png)

上記の結果から、提示された情報はTodoRespository.csの実行結果ではなく、インターセプトされた内容であることがわかります。また、私はSqlServiceをインストールしていません。

(GPTによる翻訳)