讲师中心 微信公众号
AI工具推荐 视频效率加速

C# 强类型ID实现方法 C#如何避免原始类型滥用(Primitive Obsession)

陌涛小哥_8921

陌涛小哥_8921

发布时间:2026-02-13 10:49:02

|

878人浏览过

|

来源于php中文网

原创

record struct 是封装强类型 ID 的最简实践,零堆分配、值语义安全、编译器自动实现相等性与哈希,支持隐式转换且杜绝 OrderId 与 CustomerId 误用。

c# 强类型id实现方法 c#如何避免原始类型滥用(primitive obsession)

用 record struct 封装 ID 类型最简实践

强类型 ID 的核心目标不是“看起来高级”,而是让编译器能拦住 OrderId 和 CustomerId 之间误传、误加、误比较。C# 12 的 record struct 是目前最轻量且零开销的方案——它不分配堆内存,支持值语义,还能自定义 ToString() 和隐式转换。

常见错误是直接继承 struct 并手动实现 IEquatable<t></t> 和运算符重载,既冗长又容易漏掉 GetHashCode() 或比较逻辑不一致。而 record struct 自动生成这些,只用聚焦业务含义:

public record struct OrderId(Guid Value)
{
    public static implicit operator Guid(OrderId id) => id.Value;
    public static implicit operator OrderId(Guid value) => new(value);
}

这样写后,OrderId 和 Guid 可隐式互转,但 OrderId + CustomerId 编译不过,void Process(OrderId id) 也不会被传入裸 Guid 而不加提示。

为什么不用 class 或普通 struct 封装 ID

用 class(如 public sealed class OrderId)会引入不必要的堆分配和 GC 压力,尤其在高吞吐订单系统中,ID 频繁创建/传递时性能敏感;用裸 struct 则丢失相等性语义——两个同值 OrderId 默认按字段逐位比较,看似安全,但一旦后续加字段(比如加个 Version),相等逻辑就意外改变,且无法控制 ToString() 输出格式。

record struct 同时规避了这两点:值类型无 GC,且相等性、哈希、打印行为由编译器基于构造参数(这里是 Value)稳定生成,不随内部字段增减漂移。

  • 别给 ID 类型加无意义的属性(如 IsInvalid),这会让使用者困惑“那到底该不该用 == 判断?”
  • 避免在 ID 类型里塞业务逻辑(如 IsValidForRegion(Region r)),ID 是标识符,不是领域对象
  • 如果需要序列化,确保 JSON 库(如 System.Text.Json)已配置支持 record struct 的隐式转换,否则可能序列化成 {"Value":"..."} 而非纯字符串

与 EF Core 集成时绕过原始类型映射陷阱

EF Core 默认把 OrderId 当复杂类型处理,导致迁移生成多余表字段或报错 The property 'Order.Id' is of type 'OrderId' which is not supported by the current database provider。必须显式告诉它“这个 struct 就是 Guid 的别名”:

modelBuilder.Entity<Order>()
    .Property(e => e.Id)
    .HasConversion<OrderId, Guid>(
        id => id.Value,
        value => new OrderId(value));

注意这里用的是 HasConversion<orderid guid></orderid>,不是 HasConversion<guid></guid>——后者会丢失类型信息,反向转换时无法重建 OrderId 实例。同时确保 OrderId 有公开的 Value 属性且类型为 Guid,否则转换委托编译失败。

  • 不要用 [Column(TypeName = "uniqueidentifier")] 单独标注属性,EF Core 会忽略它,仍尝试映射整个 struct
  • 如果数据库字段名不是 Id(比如叫 order_id),需额外调用 .HasColumnName("order_id"),顺序无关紧要
  • 查询时用 Where(x => x.Id == someOrderId) 能正常翻译为 SQL,但 someOrderId.ToString() 在表达式树中不会被识别,得先提取 someOrderId.Value

测试时如何验证 Primitive Obsession 是否真正消除

真正的消除标志不是“代码能编译”,而是“改一个 ID 类型定义后,编译器立刻报出所有误用位置”。例如把 OrderId 构造参数从 Guid 改成 long,以下几处必须全部爆红:

  • 所有 new OrderId(someGuid) 调用
  • 所有 ProcessOrder(OrderId id) 被传入 Guid 的地方(即使有隐式转换,改类型后转换也失效)
  • Dictionary<orderid ...></orderid> 和 Dictionary<guid ...></guid> 之间的赋值或参数传递

如果某处没报错,说明那里还存在原始类型泄漏——比如方法签名用了 Guid id 而非 OrderId id,或 DTO 中暴露了 public Guid Id { get; set; }。这种地方正是运行时 bug 的温床,比如前端传错 ID 格式,后端却因类型宽松而静默接受。

最易被忽略的是日志和调试输出:哪怕业务层用了 OrderId,如果 logger.LogInformation("Processing {OrderId}", orderId) 里 orderId 被自动拆箱成 Guid 打印,日志里就失去类型上下文,排查时无法区分这是订单 ID 还是用户 ID。

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

讯飞智文

一款面向学习和办公场景的AI文档创作工具,可辅助生成PPT与Word文档,提高资料整理和内容制作效率。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

4843

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

991

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

1269

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

6881

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

3563

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

6860

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

9541

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

1290

2024.04.29

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

0

2026.11.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
XORM框架事务处理手册
XORM框架事务处理手册

共0课时 | 0人学习

Revel 官方 app.conf 文档
Revel 官方 app.conf 文档

共0课时 | 0人学习

Revel 官方开发手册
Revel 官方开发手册

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn