首页 > 其他分享 >使用Roslyn的源生成器生成DTO

使用Roslyn的源生成器生成DTO

时间:2024-11-08 17:10:13浏览次数:3  
标签:DTO string AppendLine 生成器 prop ToString Roslyn var public

前言

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

并同时为我们生成一个简单的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

标签:DTO,string,AppendLine,生成器,prop,ToString,Roslyn,var,public
From: https://www.cnblogs.com/vipwan/p/18535459

相关文章

  • LogoGalleria:免费AI Logo生成器,轻松打造专属品牌标识
    摘要:LogoGalleria是一个免费且简单易用的AILogo生成工具,帮助用户无需设计经验即可快速生成专业Logo,适合创业公司、YouTube频道、个人项目等多种用途。我最近使用了LogoGalleria,一个专为各类用户设计的免费AILogo生成器。这个平台对我这样的非设计专业者而言非常友好。只需要输......
  • VO/DTO/DO/PO通俗的解释加上自己的理解
    通俗的解释:VO:(ViewObject):视图对象,用于展示层。DTO(DataTransferObject):数据传输对象DO(DomainObject):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。PO(PersistentObject):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系。以下为自己的......
  • Python学习笔记-生成器的应用与原理
    生成器是Python中一种特殊的迭代工具,通过延迟计算的方式来逐步生成序列中的元素。这种特性使得生成器在处理大数据、无限序列或需要惰性求值的场景中十分有效。生成器的核心思想是通过yield语句逐步返回值,暂停并保留当前状态,直到下次调用继续执行,从而节省内存并优化性能......
  • 0基础学Python——类的单例模式、反射函数、记录类的创建个数、迭代器、生成器及生成
    0基础学Python——类的单例模式、反射函数、记录类的创建个数、迭代器、生成器及生成器练习类的单例模式定义代码演示反射函数代码演示记录类的创建个数迭代器定义特点生成器定义特点写法生成器练习生成器生成1-无穷的数字生成器生成无穷个素数类的单例模式定义......
  • 自然语言处理进阶手册--藏头诗生成器
    藏头诗生成器诗词生成原理首先观察以下诗句,你觉得写得怎么样,有什么发现吗?‘深宫娥向秦人间’,‘度江水辽天帝自’,‘学士大征鞍马嘶’,‘习气秪鬻不回首’‘深坞帛头泷吏问’,‘度春水一望一相’,‘学养养子君一枝’,‘习不见一年一夜’没错,这是两首“七言绝句”......
  • JavaScript的迭代器和生成器
    1.迭代器Iterator1. 基本概念JavaScript表示集合的对象大致有Object,Array,Map,Set四种,并且这四种类型的数据之间可以相互以成员嵌套(如Array的成员可以是Object,而Map又可以嵌入Object的成员中),为了处理所有不同的数据结构,就需要统一的接口机制。迭代器(Iterator)就是这样一种......
  • 在使用echarts绘制图表时, 如果需要使用渐变色, 则应使用echarts内置的渐变色生成器ec
    series:[{name:'',type:'bar',barMaxWidth:20,label:{show:true,color:'#fff',},showBackground:true,backgroundStyle:{color:'#d5f1f9&......
  • 随机性、熵与随机数生成器:解析伪随机数生成器(PRNG)和真随机数生成器(TRNG)
    随机性在诸多领域中扮演着至关重要的角色,涵盖密码学、仿真和机器学习等方面。因为随机性为无偏决策、不可预测序列和安全加密提供了基础。然而生成随机数是一项复杂的任务,理解伪随机数生成(pseudo-randomnumbergeneration,PRNG)与真随机数生成(truerandomnumbergeneration......
  • fast-poster 海报生成器,一分钟完成海报开发.
    一.项目介绍        FastPoster是一个专为快速开发海报而设计的工具,它允许开发者只需上传背景图并在指定位置放置文本、图片、二维码或头像等组件就能生成海报。该项目提供了一键生成各种语言SDK调用代码的功能,简化了海报制作流程。经过多年的生产环境测试,FastPo......
  • CMake 生成器表达式---条件表达式和逻辑运算符
    【写在前面】CMake的生成器表达式用于在构建系统级别上进行条件判断和逻辑运算,它们通常用在目标属性和生成器表达式上下文中。这些表达式允许你根据不同的平台、配置或编译器来定制构建过程。本文引用的文档链接:cmake生成器表达式(7)—CMake3.26.4Documentation【正文......