Dapper不管理连接状态,必须显式调用connection.Open();字段名严格区分大小写且不自动映射;多结果集须用QueryMultiple()并按序Read;参数化仅支持匿名对象等安全传参方式。

连接没打开就调用Query(),直接报错
Dapper 不管理连接状态,connection.Open() 必须显式调用,否则 Query<T>()、Execute() 等方法会抛出 InvalidOperationException: Connection is not open。
- 常见写法错误:
using var conn = new SqlConnection(cs); conn.Query<User>("...");——conn还没Open()就执行查询 - 正确顺序:先
conn.Open(),再执行任何数据库操作 - 如果从
DbContext.Database.GetDbConnection()拿连接,务必检查conn.State == ConnectionState.Open,EF Core 可能已关闭它 -
using块只保证连接最终释放,不保证它在查询时是打开的
Query() 返回空列表或字段全为默认值,大概率是列名没对上
Dapper 默认按字面精确匹配(大小写敏感),不会自动做下划线转驼峰、也不会读取 [Column] 或 [JsonProperty] 等属性标记。
- PostgreSQL/MySQL(
lower_case_table_names=0)中,user_name和UserName完全不等价 - 最稳妥做法:SQL 里用别名对齐,例如
SELECT user_name AS UserName, created_at AS CreatedAt FROM users - 想全局统一处理,得手动注册类型映射器,如
SqlMapper.SetTypeMap(typeof(User), new ColumnAttributeTypeMapper<User>()),但 Dapper 本身不提供现成实现 -
QueryFirstOrDefault<T>()在无结果时返回default(T)(引用类型为null),不是异常,别误判为失败
多结果集必须用 QueryMultiple(),且 Read() 顺序不能错
QueryMultiple() 是唯一支持单次往返多个 SELECT 的方式,但它不智能——只是按 SQL 语句顺序切分结果集,游标不可回退、不可跳过。
- SQL 必须用分号分隔,例如:
"SELECT * FROM Orders; SELECT * FROM OrderItems WHERE OrderId IN @ids" - 必须严格按顺序调用:
multi.Read<Order>()→multi.Read<OrderItem>(),中间插个Read<string>()或漏掉一个,立刻崩 - 每个
Read<T>()返回的是IEnumerable<T>,不是单个对象;要取首项得接.FirstOrDefault() -
GridReader必须using包裹,否则连接不释放,容易连爆
参数化不是“看着像就行”,拼字符串就是裸奔
Dapper 只在你传入匿名对象、IDictionary<string, object> 或 DynamicParameters 时才真正参数化。任何字符串拼接,哪怕用了 $"",都等于把 SQL 注入大门敞开。
- 危险写法:
conn.Query<User>($"SELECT * FROM Users WHERE Name = '{name}'")——name = "admin'; DROP TABLE Users; --"就完蛋 - 安全写法:
conn.Query<User>("SELECT * FROM Users WHERE Name = @name", new { name = inputName }),@name和new { name = ... }属性名必须完全一致(大小写敏感) - 传
null值时,Dapper 自动转为DBNull.Value,无需手动判断 - 需要输出参数、表值参数或复杂类型映射?必须换用
DynamicParameters,匿名对象撑不住
最容易被忽略的其实是控制权移交:Dapper 从不帮你开连接、不改你的 SQL、不猜你字段名、也不替你决定什么时候释放资源。所谓“轻量”,本质是你写了什么,它就发什么;你关连接的时机,直接决定数据能不能拿到。


















