前言

源生成器的好处很多, 通过在编译时生成代码,可以减少运行时的反射和动态代码生成,从而提高应用程序的性能, 有时候需要对程序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的更多相关文章

  1. CommunityToolkit.Mvvm8.1 viewmodel源生成器写法(3)

    本系列文章导航 https://www.cnblogs.com/aierong/p/17300066.html https://github.com/aierong/WpfDemo (自我Demo地址 ...

  2. 自动生成DTO(Sugar框架)

    step1:启动api项目 step2:使用postman工具,填上接口地址http://localhost:7788/api/automapper/AutoMapperSuper step3:表格数 ...

  3. MyBatis学习---使用MyBatis_Generator生成Dto、Dao、Mapping

    由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...

  4. MyBatis---使用MyBatis Generator生成Dto、Dao、Mapping

    由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...

  5. MyBatis学习4---使用MyBatis_Generator生成Dto、Dao、Mapping

    由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...

  6. 使用MyBatis_Generator生成Dto、Dao、Mapping

    由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...

  7. 使用MyBatis_Generator工具jar包自动化生成Dto、Dao、Mapping 文件

    由于MyBatis属于一种半自动的ORM框架,所以主要的工作将是书写Mapping映射文件,但是由于手写映射文件很容易出错,所以查资料发现有现成的工具可以自动生成底层模型类.Dao接口类甚至Mappi ...

  8. 自动生成DTO(EF框架)

    [0]安装相关工具包 PostgreSQL版本: Npgsql.EntityFrameworkCore.PostgreSQL Npgsql.EntityFrameworkCore.PostgreSQL ...

  9. centos6.6 安装MariaDB

    参考文章:yum安装MariaDB(使用国内镜像快速安装,三分钟安装完毕) 安装环境: virtualbox下CentOS6.6(32位) 遇到的问题: 通过Maria官方提供的安装方式,源是国外的源 ...

  10. 知物由学 | AI网络安全实战:生成对抗网络

    本文由  网易云发布. “知物由学”是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道.“知物由学” ...

随机推荐

  1. iframe出现双层滚动条解决方案

    在开发中出现一个iframe双滚动条问题,查了很多资料,网上的解决方案是定义iframe的onload事件,加载时重新加载高度,但是由于项目中页面渲染完成后,点击按钮动态生成数据,这时候上面的方案就不 ...

  2. TL431

    1. 设置分流电压VKA 由于分流电压调节器是非理想的: 存在动态电阻ZKA,会导致VREF存在一个偏置电压: ( IKA - INOM ) × ZKA 其中,INOM 是 VKA = VREF 测试 ...

  3. Go进程内存占用那些事(一)

    为什么要探究这个问题? 作为基础设施供应商,自己的服务占用多少内存,为什么要占用这么多内存,需要能说的清楚.作为一个云计算开发,这点问题都弄不清楚,说不过去. § 0x01 范围 讨论的只限于Linu ...

  4. P7706 文文的摄影布置 题解

    P7706 文文的摄影布置 题解 原题 读完题,发现是线段树.单点修改+区间查询. 不过查询的值有些奇怪,就是了,我们考虑用线段树维护这个 ψ 值(下称待求值). 对于一个区间的待求值,大概有四种情况 ...

  5. .NET Core学习笔记(6)——UWP略过SSL证书调用SignalR服务

    在前一篇<.NET Core学习笔记(5)--WebAPI从Server端push消息到Client>中,我们简单学习了.NET Core版本SignalR的使用.Sample工程里我们创 ...

  6. Spherical Voxelization

    Spherical Voxelization 标签: voxelization AI 摘要: 文档介绍了球面体素化的过程,包括重要的类和方法,如ConvertToSphericalVoxel和sphe ...

  7. draw.io 使用自定义字体

    draw.io 是我最常用的作图工具.然而,draw.io 支持的字体很少,只支持少数英文字体.不过好在 draw.io 提供了自定义选项,我们可以填入自己想要使用的字体的名称,然后 draw.io ...

  8. sicp每日一题[1.41]

    Exercise 1.41 Define a procedure double that takes a procedure of one argument as argument and retur ...

  9. Serilog文档翻译系列(四) - 结构化数据

    Serilog 是一种序列化器.在许多情况下,它具有良好的默认行为,能够满足其目的,但有时也需要指示 Serilog 如何存储附加到日志事件上的属性. Serilog 使用一些不寻常的术语来指代 .N ...

  10. 闲的蛋疼整理了一下Dockerfile的命令和参数备查

    Dockerfile 主要指令及参数: 指令 主要参数 作用 用法示例 FROM <image>[:<tag>] [AS <name>] 指定基础镜像 FROM u ...