看看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。
文章来源微信公众号
想要更快更方便的了解相关知识,可以关注微信公众号
****欢迎关注我的asp.net core系统课程****
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524
【推荐】国内首个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