如何用Dapper映射OVER(PARTITION BY)查询结果并填充CurrentInstance属性
问题描述
我需要获取每个Configuration的最新Instance(按CreateDate降序),使用的SQL查询如下:
SELECT Configurations.*, inst.* FROM ( SELECT Instances.*, ROW_NUMBER() OVER (PARTITION BY Configurations.ID ORDER BY Instances.CreateDate DESC) AS [Row Number] FROM Configurations INNER JOIN Instances on Instances.ConfigurationID = Configurations.ID ) inst INNER JOIN Configurations ON Configurations.Id = inst.ConfigurationId WHERE inst.[Row Number] = 1
我的Configuration模型定义:
[ExplicitKey] public int Id { get; set; } public string Name { get; set; } = ""; //other params [Write(false)] public virtual Instance CurrentInstance { get; set; } = new Instance(); [Write(false)] public virtual ICollection<Instance> Instances { get; set; } = new HashSet<Instance>();
Instance与Configurations存在外键关联,Dapper请求代码如下:
var sql = "SELECT Configurations.*, inst.* FROM (SELECT Instances.*, ROW_NUMBER() OVER(PARTITION BY Configurations.ID ORDER BY Instances.CreateDate DESC) AS [Row Number] " + "FROM Configurations INNER JOIN Instances on Instances.ConfigurationID = Configurations.ID) inst " + "INNER JOIN Configurations ON Configurations.Id=inst.ConfigurationId " + "WHERE inst.[Row Number] = 1"; return (await connection.QueryAsync<Models.Configuration, Instance, Models.Configuration>(sql, (config, instance) => { config.CurrentInstance = instance; //config.Instances.Add(instance); return config; }, splitOn: "ID").ConfigureAwait(false)).ToList();
已确认SQL返回正确结果,但Dapper绑定模型时触发访问冲突(错误码0xc0000005),移除CurrentInstance属性则正常。请问如何配置模型或映射以正确填充CurrentInstance?
解决方案
出现访问冲突的核心原因是Dapper映射逻辑与模型初始化、虚拟化属性的交互产生了内存异常,可通过以下方式解决:
1. 移除CurrentInstance的默认初始化
提前初始化CurrentInstance会和Dapper自动创建的实例产生冲突,将默认初始化代码移除:
[Write(false)] public virtual Instance CurrentInstance { get; set; } // 去掉= new Instance()
2. 精确指定SplitOn参数
SQL返回结果中包含两个Id字段(Configuration和Instance的主键),默认splitOn: "ID"会在第一个Id处分割,需明确指定分割位置:
splitOn: "Id,Id"
这样Dapper会先完成Configuration的映射,再从第二个Id字段开始映射Instance。
3. 取消CurrentInstance的虚拟化(若无需懒加载)
如果不需要ORM的懒加载功能,移除virtual修饰符,避免Dapper触发动态代理逻辑:
[Write(false)] public Instance CurrentInstance { get; set; } = new Instance();
4. 自定义类型映射(极端场景)
若以上方法无效,可创建自定义TypeHandler手动控制映射流程:
public class ConfigurationTypeHandler : SqlMapper.TypeHandler<Models.Configuration> { public override Models.Configuration Parse(object value) { // 手动解析DataReader数据,完成Configuration和CurrentInstance的赋值 var reader = (IDataReader)value; var config = new Models.Configuration { Id = reader.GetInt32(reader.GetOrdinal("Id")), Name = reader.GetString(reader.GetOrdinal("Name")) // 其他属性赋值 }; config.CurrentInstance = new Instance { Id = reader.GetInt32(reader.GetOrdinal("Id")), ConfigurationID = config.Id, CreateDate = reader.GetDateTime(reader.GetOrdinal("CreateDate")) // Instance其他属性赋值 }; return config; } public override void SetValue(IDbDataParameter parameter, Models.Configuration value) { parameter.Value = value.Id; } }
初始化时注册该Handler:
SqlMapper.AddTypeHandler(new ConfigurationTypeHandler());
内容的提问来源于stack exchange,提问作者Sean
相关产品推荐
相关产品推荐

