Dapper.AOTは、Dapperほど機能が充実しているわけではありませんが、基本的な使用ニーズを満たすことができる、.NETが発表するAOTプロジェクトに最初に応答するORMです。
以下は、実装原理のデモに過ぎません。Dapper.AOTの実装には、具体的なソースコードを参照する必要があります。基本的な実装原理は、ソースジェネレータを使用して、プロジェクト内でDapperのメソッドを使用している場所を見つけ出し、インターセプタを使用して元のメソッドを置き換え、新しいメソッドはすべて書き直され、AOTをサポートしているため、開発者が開発体験を変えることなく、または古い方法をリファクタリングする際に、シンプルでダイレクトになります。
- 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);
}
}
}
}
}
- テストプロジェクト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 ");
}
}
}
}
上記の結果から、提示された情報はTodoRespository.csの実行結果ではなく、インターセプトされた内容であることがわかります。また、私はSqlServiceをインストールしていません。
(GPTによる翻訳)