• Dapper.AOT的实现原理
  • 发布于 2个月前
  • 199 热度
    0 评论
  • pckillers
  • 0 粉丝 36 篇博客
  •   
Dapper.AOT可以是第一时间响应.NET发布AOT项目的ORM,虽然不像Dapper功能完善,但也基本能满足使用。还记得我在拦截器Interceptors中最后的提示吗?当源生成器遇上拦截器后,就迸射出爱的火花,Dapper.AOT就是它们爱的结晶。

下面仅是一个实现原理的Demo,Dapper.AOT的实现,还要看它具体的源码。基本实现原理就是:用源生成器,找到项目中使用Dapper的方法的地方,利用拦截器替换掉原来的方法,新方法都是重写了的,支持AOT,所以让开发人员不改变开发体检,或重构旧方法时,简单,直接。

1、DapperAOTGenerator源生成器项目文件
<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)
        {
             // 堆代码 duidaima.com
            // 注册一个语法树遍历器,用于在语法树中查找目标方法调用
            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项目文件
<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。
用户评论