看看Dapper.AOT的实现原理
Dapper.AOT可以是第一时间响应.NET发布AOT项目的ORM,虽然不像Dapper功能完善,但也基本能满足使用。
还记得我在拦截器Interceptors中最后的提示吗?当源生成器遇上拦截器后,就迸射出爱的火花,Dapper.AOT就是它们爱的结晶。
拦截器
桂素伟,公众号:桂迹.net8:拦截器Interceptors
下面仅是一个实现原理的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)
{
// 注册一个语法树遍历器,用于在语法树中查找目标方法调用
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。
文章来源微信公众号
想要更快更方便的了解相关知识,可以关注微信公众号
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
2022-02-21 .net LTS3.1升5.0和LTS6.0隐蔽的坑
2022-02-21 应用内moniter
2017-02-21 数组,一维数组,二维数组,交错数组