惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
宝玉的分享
宝玉的分享
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Exploit Database - CXSecurity.com
Help Net Security
Help Net Security
腾讯CDC
Project Zero
Project Zero
C
CXSECURITY Database RSS Feed - CXSecurity.com
IT之家
IT之家
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tailwind CSS Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Threatpost
N
News | PayPal Newsroom
C
Cybersecurity and Infrastructure Security Agency CISA
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
SegmentFault 最新的问题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Proofpoint News Feed
A
Arctic Wolf
B
Blog RSS Feed
Forbes - Security
Forbes - Security
P
Privacy & Cybersecurity Law Blog
Attack and Defense Labs
Attack and Defense Labs
V2EX - 技术
V2EX - 技术
P
Proofpoint News Feed
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
阮一峰的网络日志
阮一峰的网络日志
aimingoo的专栏
aimingoo的专栏
T
Tenable Blog
MyScale Blog
MyScale Blog
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
WordPress大学
WordPress大学
W
WeLiveSecurity
D
DataBreaches.Net
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
罗磊的独立博客
The Last Watchdog
The Last Watchdog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Vulnerabilities – Threatpost
美团技术团队
Microsoft Security Blog
Microsoft Security Blog

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
SourceGenerator之扑风捉影
xiangji · 2026-04-28 · via 博客园_首页

上一篇博文提高用投影重构PocoEmit.Mapper

开源项目PocoEmit.Mapper重构之扑风捉影

这一篇演示投影在SourceGenerator中应用

  • 这是一个基于模型类生成附属类型(比如DTO)的实用开源小工具
  • dotnet add package Hand.GeneratePoco --version 0.2.0.2-alpha

一、首先来个简单的Case

1. 原始模型类代码

public record User(int Id, string Name);

2. 用GeneratePocoAttribute标记触发代码生成的规则

[GeneratePoco(typeof(User))]
public partial class UserDto;

3. 生成代码如下

partial class UserDto
{
	public int Id { get; set; }
	public string Name { get; set; }
}

4. 投影图如下

graph LR subgraph User source-Id[Id] source-Name[Name] end subgraph UserDto projection-Id[Id] projection-Name[Name] end source-Id -->| | projection-Id source-Name -->| | projection-Name

5. 生成代码的作用

  • 避免重复代码
  • 一个系统往往属性相同或相似的相关类型很多
  • 而且如果其一修改了属性,其他类也往往要联动修改
  • 这时用代码生成就可以避免重复代码和联动修改

二、使用投影来调整代码

  • 通过Rules配置投影规则

1 增加前缀的Case

1.1 增加投影规则

  • Prefix User就是生成的属性前缀是User
[GeneratePoco(typeof(User), Rules = ["Prefix User"])]
public partial class UserDto;

1.2 生成代码如下

partial class UserDto
{
	public int UserId { get; set; }
	public string UserName { get; set; }
}

1.3 投影图如下

graph LR subgraph User source-Id[Id] source-Name[Name] end subgraph UserDto projection-UserId[UserId] projection-UserName[UserName] end source-Id -->| AddUser | projection-UserId source-Name -->| AddUser | projection-UserName

1.4 投影的作用

  • 出于代码规范或其他原因
  • 目标类型属性名可能与原始类型不太一样
  • 这时就可以用投影来解决
  • 投影可以轻松处理前缀、后缀、大小写等各种变换

2. 增加过滤的Case

2.1 增加过滤投影规则

  • Exclude: Id就是排除Id属性
  • Prefix User还是生成的属性前缀是User
[GeneratePoco(typeof(User), Rules = ["Exclude: Id", "Prefix User"])]
public partial class NewUserDto;

2.2 生成代码如下

  • 过滤掉Id属性后
  • 所以只生成了UserName属性
partial class NewUserDto
{
	public string UserName { get; set; }
}

2.3 投影图如下

graph LR subgraph User source-Id[Id] source-Name[Name] end subgraph tmp tmp-Name[Name] end subgraph NewUserDto projection-UserName[UserName] end source-Name -->| Filter | tmp-Name tmp-Name -->| AddUser | projection-UserName

3. 多投影顺序问题

3.1 调整投影规则的顺序

  • Prefix User还是生成的属性前缀是User
  • Exclude: UserId就是排除UserId属性
[GeneratePoco(typeof(User), Rules = ["Prefix User", "Exclude: UserId"])]
public partial class NewUserDto;

3.2. 生成代码如下

  • 生成代码与上一个Case是一样的
partial class NewUserDto
{
	public string UserName { get; set; }
}

3.3. 投影图如下

graph LR subgraph User source-Id[Id] source-Name[Name] end subgraph tmp tmp-UserId[UserId] tmp-UserName[UserName] end subgraph NewUserDto projection-UserName[UserName] end source-Id -->| AddUser | tmp-UserId source-Name -->| AddUser | tmp-UserName tmp-UserName -->| Filter | projection-UserName

3.4. 多投影调整顺序的作用

  • 每个投影都会生成一个结果,多投影就会产生临时结果
  • 多投影配置需要注意,需要基于前一个投影的结果来配置
  • 就像这个Case,Prefix User规则后,如果Exclude: Id是没有作用的,因为Id已经被投影为UserId了

4. Cross投影

4.1 Cross投影规则

[GeneratePoco(typeof(User), Rules = ["Cross: Prefix User"])]
public partial class UserDto;

4.2 生成代码如下

partial class UserDto
{
	public int Id { get; set; }
	public string Name { get; set; }
    public int UserId { get; set; }
    public string UserName { get; set; }
}

4.3 投影图如下

graph LR subgraph User source-Id[Id] source-Name[Name] end subgraph UserDto projection-Id[Id] projection-Name[Name] projection-UserId[UserId] projection-UserName[UserName] end source-Id -->| | projection-Id source-Name -->| | projection-Name source-Id -->| AddUser | projection-UserId source-Name -->| AddUser | projection-UserName

4.4 Cross投影的作用

  • Cross投影在投影时会保留原始属性,产生叠加的效果
  • 这里实际介绍了投影的3种效果,本代码生成器默认的是Through效果
  • 前一篇文章介绍的投影效果是一样的
  • 本次展示了投影在不同的场景下的应用

三、 属性类型简化

  • 领域模型的属性往往封装了DP(Domain Primitive),而目标类型更适合使用原始类型
  • 这也是该项目名叫GeneratePoco的原因之一,能简就简

1. 领域模型代码

  • 领域模型的属性往往使用DP(Domain Primitive)来封装
  • IEntityId和IEntityProperty接口应用于封装DP可以方便与原始类型的相互转换
public class UserEntity(UserId id, UserName name)
    : IEntity<UserId>
{
    public UserId Id { get; } = id;
    public UserName Name { get; } = name;
}

public record struct UserId(long Original) : IEntityId;
public record struct UserName(string Original) : IEntityProperty<string>;

2. 目标类型代码

[GeneratePoco(typeof(UserEntity), Rules = ["Prefix User"])]
public partial class UserViews;

3. 生成代码如下

  • 领域模型的属性如果封装了DP,会把DP的Original属性投影到目标类对应属性上
  • 在领域建模之外使用原始类型更方便
partial class UserViews
{
    public long UserId { get; set; }
    public string UserName { get; set; }
}

四、配置可空类型

  • 特别是DTO参数类型,非必填参数可以设置为可空,后端逻辑再按一定规则填充默认值
  • NullableRule配置

1. 先预设模型类如下

public class User(int id, string name, string email, int sex)
{
    public int Id { get; } = id;
    public string Name { get; } = name;
    public string Email { get; } = email;
    public int Sex { get; } = sex;
}

2. 配置可空字段列表的Case

2.1 目标类型代码

  • 以下配置UserEmail和UserSex为可空
[GeneratePoco(typeof(User), Rules =
[
    "Prefix User"
], NullableRule = "UserEmail UserSex")]
public partial class UserDto;

2.2 生成代码如下

partial class UserDto
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public string? UserEmail { get; set; }
    public int? UserSex { get; set; }
}

3. 配置所有属性都可空的Case

3.1 目标类型代码

  • NullableRule配置为ALL
[GeneratePoco(typeof(User), Rules =
[
    "Prefix User"
], NullableRule = "ALL")]
public partial class UserDto;

3.2 生成代码如下

partial class UserDto
{
    public int? UserId { get; set; }
    public string? UserName { get; set; }
    public string? UserEmail { get; set; }
    public int? UserSex { get; set; }
}

4. 反向配置

  • 如果需要配置为空的字段太多,可以使用反向配置
  • 通过Exclude: 前缀进行反向配置

4.1 目标类型代码

  • NullableRule配置为Exclude: UserId
[GeneratePoco(typeof(User), Rules =
[
    "Prefix User"
], NullableRule = "Exclude: UserId")]
public partial class UserDto;

4.2 生成代码如下

  • 除反向配置的UserId外其他都设置为可空类型
partial class UserDto
{
    public int UserId { get; set; }
    public string? UserName { get; set; }
    public string? UserEmail { get; set; }
    public int? UserSex { get; set; }
}

五、 处理Attribute

  • Attribute很重要,有时是必不可少的
  • 比如参数的校验Attribute
  • 比如数据库存储的类型Attribute
  • 这些虽然对模型类(领域模型)逻辑上无用,语义上还是有用的
  • 其他关联类型由模型类生成就可以把相关Attribute都放在模型类(领域模型)上统一管理

1. 源类型代码

public class User(int id, string name, string email)
{
    public int Id { get; } = id;
    [Required]
    [StringLength(100, MinimumLength = 6)]
    public string Name { get; } = name;
    [EmailAddress]
    public string Email { get; } = email;
}

2. 目标类型代码

[GeneratePoco(typeof(User), Rules = ["Prefix User"])]
public partial class UserDto;

3. 生成的代码

  • 源类对应属性如果有Attribute,会把这些Attribute也投影到目标类对应属性上
partial class UserDto
{
    public int UserId { get; set; }
    [Required]
    [StringLength(100, MinimumLength = 6)]
    public string UserName { get; set; }
    [EmailAddress]
    public string UserEmail { get; set; }
}

六、例外处理

  • 某些情况无法或者很难用规则配置,就使用例外处理

1. 先预设模型类如下

public class User(int id, string name, string email, int sex)
{
    public int Id { get; } = id;
    public string Name { get; } = name;
    public string Email { get; } = email;
    public int Sex { get; } = sex;
}

2. 目标类型代码

  • Sex原始类型是int,接收参数是可空字符串类型
  • 这时直接写死就好了
  • 代码生成器会忽略已经重名的属性
[GeneratePoco(typeof(User), Rules = [ "Prefix User"])]
public partial class UserDto
{
    public string? UserSex { get; set; }
}

3. 生成代码如下

partial class UserDto
{
    public int UserId { get; set; }
    public string? UserName { get; set; }
    public string? UserEmail { get; set; }
}

七、实现原理

1. 使用SyntaxTree简化语法

2. 基于partial范式

3. 集成投影模块

3.1 MemberValidation模块解析配置

  • MemberRecognizeParsere类解析配置字符串,投影规则
  • Cross:开头的解析为Cross投影
  • Through:开头的解析为Through投影
  • Filter:开头的解析为Filter投影
  • 非以上前缀的尝试解析为Through投影
  • 解析失败的再尝试解析为IValidation规则(相当于Filter)

4. 迁移Attribute

  • Attribute从原模型类的属性迁移到目标类型上看上去简单,就是复制
  • 实现上笔者还是费了些周折
  • 从原模型类拿到的是AttributeData,需要转化为AttributeSyntax
  • AttributeData由TypedConstant构成
  • TypedConstant需要转化为AttributeArgumentSyntax
  • TypedConstant由四种,分别是Primitive、Enum、Type和Array
  • 最难搞的就是Enum,处理枚举又要反射枚举字段,再用枚举值去匹配再拼接表达式
  • 所以迁移Attribute并不是简单的复制
  • 而是先拆迁(拆解)再重建

八、总结

  • 以上规则基本都是可以排列组合使用的
  • 通过代码生成可以减少重复属性的定义,同时减少重复属性重构时要修改多个类的问题
  • 间接解决DTO类型复杂的继承关系(修改DTO继承新建DTO等)
  • 所有的依赖模型类来生成,代码更简单明了

投影及规则解析库的源码地址:
github: https://github.com/donetsoftwork/HandCore.net
gitee同步更新:https://gitee.com/donetsoftwork/HandCore.net

代码生成器及partial范式和SyntaxTree简化语法源码地址
源码托管地址: https://github.com/donetsoftwork/Hand.Generators
gitee同步更新:https://gitee.com/donetsoftwork/hand.-generators

感兴趣的同学可以去看看源码,欢迎star和pr