1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

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

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

  1. DapperAOTGeneratorソースジェネレータプロジェクトファイル。
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ソースジェネレータ実装
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);
                }
            }        
        }      
    }
}
  1. テストプロジェクトDapperAOTAPITestプロジェクトファイル
<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

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class InterceptsLocationAttribute(string filePath, int line, int character) : Attribute
    {
    }
}

Dapperを使用するクラスTodoRespository.cs

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

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

(GPTによる翻訳)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?