- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
源生成器的好处很多, 通过在编译时生成代码,可以减少运行时的反射和动态代码生成,从而提高应用程序的性能, 有时候需要对程序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 。
最后此篇关于使用Roslyn的源生成器生成DTO的文章就讲到这里了,如果你想了解更多关于使用Roslyn的源生成器生成DTO的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我看到 DTO 类型在域模型中的类型内创建并在类型之间传递。这是好习惯吗? 我一直认为 DTO 主要用于上下文边界(即对象图的边缘)以解耦上下文实现(例如域/ui 边界)。 最佳答案 你的问题有点主观
我们将使用 DTO 向表示层发送数据或从表示层发送数据。我们有这样的层: 外观 应用服务 域名 我们使用 Dozer 来帮助我们将实体转换为 dto。但我现在有两个问题: 从entity到dto我们可
我对术语有疑问。根据 Fowler 的说法,DTO 是“在进程之间传输数据以减少方法调用次数的对象”。 (http://martinfowler.com/eaaCatalog/dataTransfer
我们使用 spring-boot 开发的应用程序遵循微服务架构。为了解释这个问题,让我们考虑 3 个不同的服务 A、B、C。服务 A 和 B 也使用服务 C 的一些 API。 我在项目 C(服务 C)
所以基本上我正在编写一个使用 DTO 的 API,但我在返回 DTO 内的另一个实体时遇到了问题。 这是我的 DTO: public class DirectoryDTO { String per
我尝试从方法响应正文中获取派生类字段。请求体参数是基类的类型。请求带有派生类字段,但我无法将其转换为派生类。 这是我的 Controller 方法和 DTO 类: 方法: @PostMapping(
这更多的是一个理论问题,而不是一个实际问题。 我们有一个分层架构,类似于: UI UI_JavaHandler Backend DTO1 需要比 DTO2 多一点的数据,并且恰好是一个额外的字符串
我在 Wildfly 10.1.0-Final 上使用带有 Java 8 和 Hibernate (5.0.X) 的 Java EE 7,我需要使用投影将 JPQL 查询结果加载到 DTO 中,但我找
有一个建议是transfer objects should not contain object references to other transfer objects .相反,他们应该使用其他传输
我们正在开始一个新项目并正在设计 DTO,这些 DTO 将被注入(inject)到具有行为的相应 POCO 中。然而,我能找到的每个 DTO 示例都只包含值类型,例如: public class Cu
这可能是一个一般的java问题 DTO1 属性1 属性2 属性3 DTO2 属性1 属性2 属性3 属性4 属性5 属性6 属性7 属性8 属性9 属性10 属性11 属性12 我将在屏幕上的 gxt
我在一个项目中遇到了一个问题,并在一个裸测试项目中成功地重现了它。 我有以下 dto: public class AppUserDto { public int Id { get; set;
我正在研究 RESTful API,但在为 API 提供输入的过程中遇到了一些麻烦。 假设我有一个可以像这样获取的“人”资源:api/person/{id}并返回一个这样的对象: public cla
我正在使用 DTO 构建我的第一个应用程序 - 我目前有一个 DTO 用于获取特定对象的数据,另一个不同的 DTO 用于更新(PUTting)数据 - 因为只有少数字段可以从任何客户端更新,我决定为
private void writeData(HSSFSheet sheet) { for (int i = 0; i 并动态获取 DTO 属性值?,我们在Stack Overflo
我正在尝试使用 Jackson 和 Kotlin 将 YAML 文档映射到复杂的 DTO 结构,但似乎在某处遇到了误解。 我正在解析的 YAML 文档是 item_names: - item:
这个问题在这里已经有了答案: How to efficiently create a list of objects inside of a generic method? (2 个答案) 关闭 9
我们将使用 DTO 向表示层发送数据或从表示层发送数据。 我在名为 PostAd 的服务对象上有一个方法,它发布用户输入的广告。 Ad 与另一个名为 AdValues 的对象相关联,该对象包含 Ad
我的应用程序服务中有验证逻辑,用于确定请求的操作是否成功,如果没有,原因。我质疑这是否是代码异味,因为它在应用程序服务中而不是域服务中,它围绕检查域模型是否存在、dto 中的属性是否可以为空等展开。代
我有以下域模型: public class Playlist { public long Id { get; set; } public string Title { get; set
我是一名优秀的程序员,十分优秀!