C# Dapper Contrib使用方法 C#如何简化Dapper的CRUD操作

2次阅读

dapper.contrib 报“id Property not found”因默认只识别名为 id 的 int/long 主键;需用 [key] 显式标记主键(支持 guid 等),[explicitkey] 用于手动赋值主键,[computed] 标注数据库计算列。

C# Dapper Contrib使用方法 C#如何简化Dapper的CRUD操作

为什么直接用 Dapper.Contrib 会报错“ID property not found”

因为 Dapper.Contrib 要求实体类必须有明确的主键标识,且默认只认名为 Idintlong 类型属性。如果你的主键叫 UserIdOrderId,或者类型是 Guid,不加标注就会失败。

解决方式是显式标记主键:

  • [Key] 特性标注主键字段(支持 Guidintlong 等)
  • [ExplicitKey] 标注需要手动赋值的主键(比如数据库不自增,由代码生成 Guid
  • [Computed] 标注数据库计算列(如 CreatedAt 默认 GETDATE()),避免插入时传入

示例:

[table("Users")] public class User {     [Key]     public Guid Id { get; set; } // 不再依赖命名或类型约束          public String Name { get; set; }          [Computed]     public DateTime CreatedAt { get; set; } }

InsertUpdate 为什么会忽略某些字段

Dapper.Contrib 默认跳过 NULL 值、空字符串、0 值(对数值类型)以及未标记为可写([Write(true)])的字段。它不是全量映射,而是按“有意义变更”策略处理。

常见陷阱:

  • bool IsActive = false 被当成“未设置”,插入时不会写入数据库 —— 必须加 [Write(true)]
  • string Code = ""(空字符串)被跳过,而你其实想存空值 —— 改用 string.Empty 不起作用,需显式赋值并确保字段可写
  • DateTime? LastLogin = null 是安全的,但 DateTime LastLogin = default(即 0001-01-01)会被写入,可能不是你想要的

推荐做法:所有需要持久化的字段,显式加 [Write(true)],尤其是布尔、数值、日期等易被误判的类型。

如何让 Dapper.Contrib 支持复合主键或非标准表名

它原生不支持复合主键 —— 这是硬限制。一旦实体有多个 [Key] 字段,Insert/Get 等方法会抛异常或行为未定义。必须改用单主键设计,或退回到原生 Dapper 手写 sql

表名和列名控制靠两个特性:

  • [Table("user_info")] 控制映射的物理表名(不加则用类名小写)
  • [column("user_name")] 控制字段对应列名(不加则用属性名小写)

注意:[Column] 不影响参数绑定顺序,只影响生成的 SQL 中的列名;如果字段名含大小写或特殊字符,必须加该特性,否则生成的 SQL 可能语法错误(如列名未加括号或引号)。

性能与线程安全要注意什么

Dapper.Contrib 内部用了静态缓存(如 SqlMapperExtensions.GetTableInfo 缓存类型映射),所以首次调用 Insert 较慢,后续极快。但这也意味着:一旦类结构变更(如加字段、改 [Key]),缓存不会自动刷新 —— 开发期容易查不到新字段,需重启应用或手动清缓存(无公开 API,实际中建议避免热更新实体)。

它本身是线程安全的,但依赖的 IDbConnection 不是。别把同一个连接实例跨线程复用;每个操作应使用新打开的连接或通过 using 确保释放。

最常被忽略的一点:它不支持事务跨方法传播。比如你在一个 TransactionScope 里调用两次 Insert,它们各自开连接、各自提交 —— 实际上没在同一个事务里。必须手动传入共享的 IDbConnectionIDbTransaction,并调用带 transaction 参数的重载方法。

text=ZqhQzanResources