使用Roslyn的源生成器生成DTO
前言
源生成器的好处很多, 通过在编译时生成代码,可以减少运行时的反射和动态代码生成,从而提高应用程序的性能, 有时候需要对程序AOT以及裁剪编译的dll也是需要用SG来处理的。
我们开发程序应该都绕不过Mapper对象映射,用的比较多的库可能就是AutoMapper,Maspter之内的三方库吧;这些库很强大但是因为内部实现存在反射,因此开发的程序就没办法AOT了,因此如果程序不是很复杂但是又有很特殊的需求,建议使用SG来实现Mapper
功能演示
这里我演示下自己开发的AutoDto生成DTO功能:
比如我们有一个User的类,需要生成UserDto
public class User
{
public string Id { get; set; } = null!;
public string FirstName { get; set; } = null!;
public string LastName { get; set; } = null!;
public int? Age { get; set; }
public string? FullName => $"{FirstName} {LastName}";
}
定义UserDto并标注特性:
[AutoDto<User>(nameof(User.Id))]//这里我们假设排除Id属性
public partial record UserDto;
就这样,源生成器将为我们生成对应的Dto:
partial record class UserDto
{
/// <inheritdoc cref = "User.FirstName"/>
public string FirstName { get; set; }
/// <inheritdoc cref = "User.LastName"/>
public string LastName { get; set; }
/// <inheritdoc cref = "User.Age"/>
public int? Age { get; set; }
/// <inheritdoc cref = "User.FullName"/>
}
并同时为我们生成一个简单的Mapper扩展方法:
public static partial class UserToUserDtoExtentions
{
/// <summary>
/// mapper to UserDto
/// </summary>
/// <returns></returns>
public static UserDto MapperToUserDto(this User model)
{
return new UserDto()
{
FirstName = model.FirstName,
LastName = model.LastName,
Age = model.Age,
FullName = model.FullName,
};
}
}
实现代码
static void GENDTO(Compilation compilation, ImmutableArray<SyntaxNode> nodes, SourceProductionContext context)
{
if (nodes.Length == 0) return;
StringBuilder envStringBuilder = new();
envStringBuilder.AppendLine("// <auto-generated />");
envStringBuilder.AppendLine("using System;");
envStringBuilder.AppendLine("using System.Collections.Generic;");
envStringBuilder.AppendLine("using System.Text;");
envStringBuilder.AppendLine("using System.Threading.Tasks;");
envStringBuilder.AppendLine("#pragma warning disable");
foreach (var nodeSyntax in nodes.AsEnumerable())
{
//Cast<ClassDeclarationSyntax>()
//Cast<RecordDeclarationSyntax>()
if (nodeSyntax is not TypeDeclarationSyntax node)
{
continue;
}
//如果是Record类
var isRecord = nodeSyntax is RecordDeclarationSyntax;
//如果不含partial关键字,则不生成
if (!node.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)))
{
continue;
}
AttributeSyntax? attributeSyntax = null;
foreach (var attr in node.AttributeLists.AsEnumerable())
{
var attrName = attr.Attributes.FirstOrDefault()?.Name.ToString();
if (attrName?.IndexOf(AttributeValueMetadataNameDto, System.StringComparison.Ordinal) == 0)
{
attributeSyntax = attr.Attributes.First(x => x.Name.ToString().IndexOf(AttributeValueMetadataNameDto, System.StringComparison.Ordinal) == 0);
break;
}
}
if (attributeSyntax == null)
{
continue;
}
//转译的Entity类名
var entityName = string.Empty;
string pattern = @"(?<=<)(?<type>\w+)(?=>)";
var match = Regex.Match(attributeSyntax.ToString(), pattern);
if (match.Success)
{
entityName = match.Groups["type"].Value.Split(['.']).Last();
}
else
{
continue;
}
var sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine($"//generate {entityName}-{node.Identifier.ValueText}");
sb.AppendLine();
sb.AppendLine("namespace $ni");
sb.AppendLine("{");
sb.AppendLine("$namespace");
sb.AppendLine("$classes");
sb.AppendLine("}");
// sb.AppendLine("#pragma warning restore");
string classTemp = $"partial $isRecord $className {{ $body }}";
classTemp = classTemp.Replace("$isRecord", isRecord ? "record class" : "class");
{
// 排除的属性
List<string> excapes = [];
if (attributeSyntax.ArgumentList != null)
{
for (var i = 0; i < attributeSyntax.ArgumentList!.Arguments.Count; i++)
{
var expressionSyntax = attributeSyntax.ArgumentList.Arguments[i].Expression;
if (expressionSyntax.IsKind(SyntaxKind.InvocationExpression))
{
var name = (expressionSyntax as InvocationExpressionSyntax)!.ArgumentList.DescendantNodes().First().ToString();
excapes.Add(name.Split(['.']).Last());
}
else if (expressionSyntax.IsKind(SyntaxKind.StringLiteralExpression))
{
var name = (expressionSyntax as LiteralExpressionSyntax)!.Token.ValueText;
excapes.Add(name);
}
}
}
var className = node.Identifier.ValueText;
var rootNamespace = string.Empty;
//获取文件范围的命名空间
var filescopeNamespace = node.AncestorsAndSelf().OfType<FileScopedNamespaceDeclarationSyntax>().FirstOrDefault();
if (filescopeNamespace != null)
{
rootNamespace = filescopeNamespace.Name.ToString();
}
else
{
rootNamespace = node.AncestorsAndSelf().OfType<NamespaceDeclarationSyntax>().Single().Name.ToString();
}
StringBuilder bodyBuilder = new();
List<string> namespaces = [];
StringBuilder bodyInnerBuilder = new();
StringBuilder mapperBodyBuilder = new();
bodyInnerBuilder.AppendLine();
List<string> haveProps = [];
// 生成属性
void GenProperty(TypeSyntax @type)
{
var symbols = compilation.GetSymbolsWithName(@type.ToString(), SymbolFilter.Type);
foreach (ITypeSymbol symbol in symbols.Cast<ITypeSymbol>())
{
var fullNameSpace = symbol.ContainingNamespace.ToDisplayString();
// 命名空间
if (!namespaces.Contains(fullNameSpace))
{
namespaces.Add(fullNameSpace);
}
symbol.GetMembers().OfType<IPropertySymbol>().ToList().ForEach(prop =>
{
if (!excapes.Contains(prop.Name))
{
// 如果存在同名属性,则不生成
if (haveProps.Contains(prop.Name))
{
return;
}
haveProps.Add(prop.Name);
//如果是泛型属性,则不生成
if (prop.ContainingType.TypeParameters.Any(x => x.Name == prop.Type.Name))
{
return;
}
// prop:
var raw = $"public {prop.Type.ToDisplayString()} {prop.Name} {{get;set;}}";
// body:
bodyInnerBuilder.AppendLine($"/// <inheritdoc cref=\"{@type}.{prop.Name}\" />");
bodyInnerBuilder.AppendLine($"{raw}");
// mapper:
// 只有public的属性才能赋值
if (prop.GetMethod?.DeclaredAccessibility == Accessibility.Public)
{
mapperBodyBuilder.AppendLine($"{prop.Name} = model.{prop.Name},");
}
}
});
}
}
// 生成属性:
var symbols = compilation.GetSymbolsWithName(entityName, SymbolFilter.Type);
var symbol = symbols.Cast<ITypeSymbol>().FirstOrDefault();
//引用了其他库.
if (symbol is null)
continue;
GenProperty(SyntaxFactory.ParseTypeName(symbol.MetadataName));
// 生成父类的属性:
INamedTypeSymbol? baseType = symbol.BaseType;
while (baseType != null)
{
GenProperty(SyntaxFactory.ParseTypeName(baseType.MetadataName));
baseType = baseType.BaseType;
}
var rawClass = classTemp.Replace("$className", className);
rawClass = rawClass.Replace("$body", bodyInnerBuilder.ToString());
// append:
bodyBuilder.AppendLine(rawClass);
string rawNamespace = string.Empty;
namespaces.ForEach(ns => rawNamespace += $"using {ns};\r\n");
var source = sb.ToString();
source = source.Replace("$namespace", rawNamespace);
source = source.Replace("$classes", bodyBuilder.ToString());
source = source.Replace("$ni", rootNamespace);
// 生成Mapper
var mapperSource = MapperTemplate.Replace("$namespace", namespaces.First());
mapperSource = mapperSource.Replace("$ns", rootNamespace);
mapperSource = mapperSource.Replace("$baseclass", entityName);
mapperSource = mapperSource.Replace("$dtoclass", className);
mapperSource = mapperSource.Replace("$body", mapperBodyBuilder.ToString());
// 合并
source = $"{source}\r\n{mapperSource}";
envStringBuilder.AppendLine(source);
}
}
envStringBuilder.AppendLine("#pragma warning restore");
var envSource = envStringBuilder.ToString();
// format:
envSource = envSource.FormatContent();
context.AddSource($"Biwen.AutoClassGenDtoG.g.cs", SourceText.From(envSource, Encoding.UTF8));
}
const string MapperTemplate = $@"
namespace $namespace
{{
using $ns ;
public static partial class $baseclassTo$dtoclassExtentions
{{
/// <summary>
/// mapper to $dtoclass
/// </summary>
/// <returns></returns>
public static $dtoclass MapperTo$dtoclass(this $baseclass model)
{{
return new $dtoclass()
{{
$body
}};
}}
}}
}}
";
最后
以上代码就完成了整个源生成步骤,最后你可以使用我发布的nuget包体验:
<ItemGroup>
<PackageReference Include="Biwen.AutoClassGen.Attributes" Version="1.3.6" />
<PackageReference Include="Biwen.AutoClassGen" Version="1.5.2" PrivateAssets="all" />
</ItemGroup>
当然如果你对完整的实现感兴趣可以移步我的GitHub仓储,欢迎star https://github.com/vipwan/Biwen.AutoClassGen
使用Roslyn的源生成器生成DTO的更多相关文章
- CommunityToolkit.Mvvm8.1 viewmodel源生成器写法(3)
本系列文章导航 https://www.cnblogs.com/aierong/p/17300066.html https://github.com/aierong/WpfDemo (自我Demo地址 ...
- 自动生成DTO(Sugar框架)
step1:启动api项目 step2:使用postman工具,填上接口地址http://localhost:7788/api/automapper/AutoMapperSuper step3:表格数 ...
- MyBatis学习---使用MyBatis_Generator生成Dto、Dao、Mapping
由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...
- MyBatis---使用MyBatis Generator生成Dto、Dao、Mapping
由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...
- MyBatis学习4---使用MyBatis_Generator生成Dto、Dao、Mapping
由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...
- 使用MyBatis_Generator生成Dto、Dao、Mapping
由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...
- 使用MyBatis_Generator工具jar包自动化生成Dto、Dao、Mapping 文件
由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...
- 自动生成DTO(EF框架)
[0]安装相关工具包 PostgreSQL版本: Npgsql.EntityFrameworkCore.PostgreSQL Npgsql.EntityFrameworkCore.PostgreSQL ...
- centos6.6 安装MariaDB
参考文章:yum安装MariaDB(使用国内镜像快速安装,三分钟安装完毕) 安装环境: virtualbox下CentOS6.6(32位) 遇到的问题: 通过Maria官方提供的安装方式,源是国外的源 ...
- 知物由学 | AI网络安全实战:生成对抗网络
本文由 网易云发布. “知物由学”是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道.“知物由学” ...
随机推荐
- springcloud集成grpc(二)
码云地址:https://gitee.com/lpxs/lp-springcloud.git 有问题可以多沟通:136358344@qq.com. 上一章内容介绍了springboot2集成net.d ...
- bazel 简介(一)—— 基础概念与原理
0x01 背景 bazel目前已广泛用于云计算领域的开源软件的构建如k8s.kubevirt等,本文以一个新手的角度分享下bazel的基础知识,其存在的价值.对比下,它与其他已经存在的构建系统的差别, ...
- C#自定义结构体的(用SendMessage)传递
要传递结构体 public struct STUDENT { public int id; //ID public string name; //姓名 } 要引用Win32api函数FindWindo ...
- python3安装虚拟环境并使用freeze命令迁移模块
python3安装虚拟环境 #1.安装虚拟环境 pip3 install virtualenv #2.创建虚拟环境 python3 -m venv venv #或者 python3 -m venv . ...
- 资产管理平台去除zabbix字样
1.主机可用性 修改/usr/share/zabbix/include/html.inc.php,文件没有改动过的话在602行,将zbx改成我们需要的即可 2.修改系统信息 修改/usr/share/ ...
- spark 怎么读写 elasticsearch
参考文章: https://www.bmc.com/blogs/spark-elasticsearch-hadoop/ https://blog.pythian.com/updating-elasti ...
- mysql修改编码utf8
摘要:使用apt-get 命令安装的mysql默认不是utf8.在这里记录一下如何将编码修改成utf8. Linux学习笔记之--ubuntu中mysql修改编码utf8 一:查看mysql版本 1. ...
- 物体检测序列之一:ap, map
准确率(Precision),也叫正确预测率(positive predictive value),在模式识别.信息检索.机器学习等研究应用领域,准确率用来衡量模型预测的结果中相关或者正确的比例.而召 ...
- 物体检测序列之一:NMS
IoU (Intersection over Union),交并比,是衡量物体检测模型在特定数据集上检测效果好坏的一个常用的标准,通常情况下,想要通过IoU来衡量物体检测模型好坏需要具备以下几点: 1 ...
- ASP.NET Core – View Component
前言 以前写过 Asp.net core 学习笔记 ( ViewComponent 组件 ), 这篇作为翻新版. 参考 Docs – View components in ASP.NET Core D ...