如何通过反射实现SqlDataReader到嵌套自定义类的映射
问题核心原因
你现有代码的核心错误是 accessorNested[mem.Type, fieldName] = rd.GetValue(i); 这行写法不符合TypeAccessor的使用规则:TypeAccessor索引器的第一个参数需要传入具体的实例对象,你传入类型本身只会尝试给静态成员赋值,完全不会把值写入你创建的主对象t的嵌套属性实例中。
此外现有逻辑还缺少嵌套实例为空的初始化逻辑,也不支持多层递归嵌套。
修复后实现
下面的实现支持单层/多层自定义类型属性映射,保留原有的大小写不敏感匹配规则:
using FastMember; using System.Data.SqlClient; internal static async Task<IEnumerable<T>> ConvertToNestedObjectsAsync<T>(this SqlDataReader rd) where T : class, new() { Type type = typeof(T); var accessor = TypeAccessor.Create(type); var members = accessor.GetMembers(); var tList = new List<T>(); while (await rd.ReadAsync()) { var t = new T(); for (int i = 0; i < rd.FieldCount; i++) { if (rd.IsDBNull(i)) continue; string fieldName = rd.GetName(i); object fieldValue = rd.GetValue(i); // 先匹配主类的直接成员 if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase))) { accessor[t, fieldName] = fieldValue; continue; } // 匹配嵌套类成员 SetNestedPropertyValue(t, fieldName, fieldValue); } tList.Add(t); } return tList; } // 递归赋值嵌套属性的辅助方法 private static void SetNestedPropertyValue(object instance, string fieldName, object value) { var instanceType = instance.GetType(); var accessor = TypeAccessor.Create(instanceType); var nonPrimitiveMembers = accessor.GetMembers().Where(m => !IsPrimitive(m.Type)).ToList(); foreach (var mem in nonPrimitiveMembers) { var nestedAccessor = TypeAccessor.Create(mem.Type); var nestedMembers = nestedAccessor.GetMembers(); // 匹配嵌套类的字段名 if (nestedMembers.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase))) { // 嵌套实例为空则先初始化 var nestedInstance = accessor[instance, mem.Name]; if (nestedInstance == null) { // 要求嵌套类型必须有无参构造函数 nestedInstance = Activator.CreateInstance(mem.Type); accessor[instance, mem.Name] = nestedInstance; } // 给嵌套实例的对应属性赋值 nestedAccessor[nestedInstance, fieldName] = value; return; } // 支持更深层级的递归匹配,比如A.B.C var deeperNestedInstance = accessor[instance, mem.Name]; if (deeperNestedInstance == null) { deeperNestedInstance = Activator.CreateInstance(mem.Type); accessor[instance, mem.Name] = deeperNestedInstance; } SetNestedPropertyValue(deeperNestedInstance, fieldName, value); } } internal static bool IsPrimitive(Type t) { return new[] { typeof(string), typeof(char), typeof(byte), typeof(sbyte), typeof(ushort), typeof(short), typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal), typeof(DateTime), typeof(Guid), typeof(DateTimeOffset), typeof(bool) }.Contains(t) || t.IsPrimitive || t.IsEnum; }
额外优化建议
- 若存在多个嵌套类有同名字段的情况,建议给查询字段加前缀区分,比如
Address_City对应Person.Address.City,匹配时按下划线拆分层级,避免冲突,也能提升匹配效率。 - 如果你需要映射
List<Fields>这类集合属性,需要基于主表的唯一键对查询结果分组:同一分组的主表字段只赋值一次,明细字段生成对应实例添加到集合中即可。
内容的提问来源于stack exchange,提问作者Justin Farrugia
相关产品推荐
相关产品推荐

