Dapper作为轻量级的ORM框架,默认映射逻辑依赖属性的公共setter方法,当实体类中的属性被定义为只读属性,也就是只有getter没有setter时,直接使用Query方法查询数据会出现属性值为默认值的情况,无法完成正常映射。这种情况在需要定义不可变实体类时非常常见,下面介绍几种可行的解决方案。

只读属性无法映射的原因
Dapper内部默认使用System.Reflection相关的API来为实体属性赋值,对于没有setter的属性,反射无法找到对应的赋值入口,因此不会将查询结果中的字段值写入到只读属性中。比如下面这个实体类,其中的Id和Name是只读属性:
public class User
{
public int Id { get; }
public string Name { get; }
public User(int id, string name)
{
Id = id;
Name = name;
}
}
如果直接执行下面的查询代码,得到的User对象的Id和Name都会是默认值:
using Dapper;
using System.Data.SqlClient;
string connStr = "Server=127.0.0.1;Database=Test;Uid=sa;Pwd=123456";
using var conn = new SqlConnection(connStr);
var users = conn.Query<User>("SELECT Id, Name FROM User");
方案一:使用构造函数映射
Dapper支持通过构造函数来初始化实体对象,只要构造函数的参数名称和查询结果的字段名称一致,就可以自动匹配参数完成对象创建,此时只读属性可以在构造函数中赋值。这种方式是最推荐的,符合不可变实体的设计理念。
上面的User类已经定义了包含id和name参数的构造函数,只需要确保查询的字段名和参数名一致即可,Dapper会自动匹配构造函数完成映射,不需要额外配置。如果构造函数的参数名和字段名不一致,可以通过别名的方式让字段名和参数名匹配:
// 查询时给字段起别名,和构造函数参数名匹配
var users = conn.Query<User>("SELECT Id AS id, Name AS name FROM User");
方案二:自定义类型映射
如果需要更灵活的映射逻辑,可以实现ICustomTypeMapper接口,自定义类型映射规则,手动为只读属性赋值。首先需要定义自定义映射类:
using Dapper;
using System;
using System.Collections.Generic;
using System.Reflection;
public class UserMapper : ICustomTypeMapper
{
public object Map(Type type, IDataRecord row) where Type : class
{
// 从查询结果中获取字段值
var id = row.GetInt32(row.GetOrdinal("Id"));
var name = row.GetString(row.GetOrdinal("Name"));
// 调用构造函数创建对象
return new User(id, name);
}
}
然后需要在查询前注册这个自定义映射:
// 注册自定义映射,指定User类型使用UserMapper
SqlMapper.SetTypeMap(typeof(User), new CustomTypeMapper<User>(new UserMapper()));
// 执行查询,此时会使用自定义映射规则
var users = conn.Query<User>("SELECT Id, Name FROM User");
方案三:使用动态类型中转
如果不想修改实体类也不想自定义映射,可以先将查询结果映射为动态类型,再手动创建实体对象赋值给只读属性。这种方式适合临时处理少量映射场景:
var dynamicUsers = conn.Query("SELECT Id, Name FROM User").ToList();
List<User> users = new List<User>();
foreach (var item in dynamicUsers)
{
users.Add(new User(item.Id, item.Name));
}
不同方案对比
下面是三种方案的适用场景和优缺点对比:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 构造函数映射 | 实现简单,无需额外配置,符合不可变设计 | 要求构造函数参数名和字段名一致,复杂场景不够灵活 | 实体结构简单,构造函数参数和字段匹配的场景 |
| 自定义类型映射 | 映射逻辑灵活,可处理复杂映射规则 | 需要额外编写映射类,配置稍复杂 | 映射规则复杂,需要自定义赋值逻辑的场景 |
| 动态类型中转 | 无需修改实体和额外配置,临时处理方便 | 需要手动遍历赋值,性能稍差,类型不安全 | 临时少量映射,不想做额外配置的场景 |
注意事项
- 使用构造函数映射时,如果实体类有多个构造函数,Dapper会尝试匹配参数最多的构造函数,尽量保证构造函数参数和查询字段一一对应。
- 自定义类型映射时,需要注意
IDataRecord获取字段值时的类型匹配,避免类型转换异常。 - 如果只读属性是值类型,没有赋值的话会显示默认值,需要确认映射逻辑是否生效。
Dapperreadonly_propertyORM映射CSharp修改时间:2026-07-11 11:12:26