为什么直接用 Dapper.Contrib
会报错“ID property not found”
因为
Dapper.Contrib要求实体类必须有明确的主键标识,且默认只认名为
Id的
int或
long类型属性。如果你的主键叫
UserId、
OrderId,或者类型是
Guid,不加标注就会失败。
解决方式是显式标记主键:
用[Key]特性标注主键字段(支持
Guid、
int、
long等) 用
[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; }
}
Insert
和 Update
为什么会忽略某些字段
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,它们各自开连接、各自提交 —— 实际上没在同一个事务里。必须手动传入共享的
IDbConnection和
IDbTransaction,并调用带 transaction 参数的重载方法。
