使用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网络安全实战:生成对抗网络
本文由 网易云发布. “知物由学”是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道.“知物由学” ...
随机推荐
- 【VMware VCF】VCF 5.2:部署整合架构的SDDC。
VMware 前不久发布了 VMware Cloud Foundation 5.2 版本,并带来了许多功能的升级,比如支持 vSAN Max 分解存储,管理工作负载域支持 vSAN ESA 延伸集群, ...
- 22.11.20 ICPC合肥站 打星记录
A,B,H签到. B题:注意区分相对误差与绝对误差!!小数相对误差小于1e-6,至少要输出十二位! G题优先队列.场上十几分钟就想出来了,表扬自己一波,留个坑位写题解. M题情况不多直接暴搜, 最后一 ...
- Spring Boot 框架中配置文件 application.properties 当中的所有配置大全
Spring Boot 框架中配置文件 application.properties 当中的所有配置大全 #SPRING CONFIG(ConfigFileApplicationListener) s ...
- B 端产品未来几年的发展趋势
在当今数字化高速发展的时代,B 端产品经理作为企业数字化转型的关键推动者,肩负着重大的责任.不仅要深入了解企业的业务需求,还要紧跟技术发展的步伐,为企业提供高效.创新的解决方案.那么,未来几年,B 端 ...
- Mac上HomeBrew安装及换源教程
Mac上HomeBrew安装及换源教程 Mac的Mac OS系统来源于Unix系统,得益于此Mac系统的使用类似于Linux,因此Linux系统中的包管理概念也适用于Mac,而HomeBrew便是其中 ...
- Qemu - 介绍
Ref: QEMU支持3种运行模式 -- 理解 https://www.minitool.com/partition-disk/qemu-for-windows.html
- Dell存储备份告警:
创建时间 修改日期 对象名称 消息 类型 告警状态 已确认 告警定义 类型 23-3-12 11:59:26 23-3-12 11:59:37 copyMirrorswap 2 CMs Operati ...
- 2023/11/16 NOIP 模拟赛
T1 基于1的算术 标签 暴力枚举 思路1 赛时想了个假的 DP,只拿了 77 分,,, 小于 \(10^{15}\) 的仅由 \(1\) 组成的数只有 \(15\) 个,直接枚举即可. 想了一个做法 ...
- Angular 18+ 高级教程 – Angular Compiler (AKA ngc) Quick View
前言 在 Get Started 那一篇,我们提到过 Angular Compilation.这篇稍微给点具体画面,让大家感受一下. 但不会讲细节,对细节感兴趣的可以看这篇 Medium – How ...
- 加入 Flutter Engage,Pick 您的专属 Dash 形象!
Flutter Engage 活动精彩来袭 对 Flutter 团队的开发者们来说,交流的重要性不言而喻,和您一样,我们也希望开发者们能够在不同的情境下进行互动分享.于是我们为您准备了一场特别的线上活 ...