如何修剪Dapper查询返回复杂对象中的所有字符串(遗留数据库场景)
解决Dapper查询自动修剪字符串末尾空格的问题
你遇到的核心问题在于:扩展方法命名不匹配原生调用、dynamic类型无法通过常规反射处理,以及现有代码未递归处理嵌套实体的字符串属性。下面一步步帮你解决:
一、修正你的Dapper扩展方法
先把扩展方法名改成你实际调用的QueryTrim,同时处理dynamic(本质是ExpandoObject)和嵌套实体的情况:
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Dynamic; using System.Reflection; using Dapper; public static class DapperExtensions { public static IEnumerable<T> QueryTrim<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { var dapperResult = SqlMapper.Query<T>(cnn, sql, param, transaction, buffered, commandTimeout, commandType); TrimAllStrings(dapperResult); return dapperResult; } private static void TrimAllStrings<T>(IEnumerable<T> items) { foreach (var item in items) { if (item == null) continue; // 处理dynamic/ExpandoObject类型 if (item is ExpandoObject expando) { TrimExpandoObject(expando); } else { // 处理普通实体类,递归遍历所有属性 TrimObjectProperties(item); } } } private static void TrimExpandoObject(ExpandoObject expando) { var dict = (IDictionary<string, object>)expando; var keys = new List<string>(dict.Keys); foreach (var key in keys) { var value = dict[key]; if (value is string strValue) { dict[key] = strValue.SafeTrim(); } // 递归处理嵌套的ExpandoObject else if (value is ExpandoObject nestedExpando) { TrimExpandoObject(nestedExpando); } // 处理嵌套集合 else if (value is IEnumerable enumerable && !(value is string)) { foreach (var nestedItem in enumerable) { if (nestedItem is ExpandoObject nested) TrimExpandoObject(nested); else TrimObjectProperties(nestedItem); } } else { TrimObjectProperties(value); } } } private static void TrimObjectProperties(object obj) { if (obj == null) return; var type = obj.GetType(); // 修剪当前对象的字符串属性 var stringProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.PropertyType == typeof(string) && p.CanRead && p.CanWrite); foreach (var prop in stringProperties) { var value = (string)prop.GetValue(obj); prop.SetValue(obj, value.SafeTrim()); } // 递归处理嵌套对象和集合属性 var nestedProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => !p.PropertyType.IsValueType && p.PropertyType != typeof(string) && p.CanRead); foreach (var prop in nestedProperties) { var value = prop.GetValue(obj); if (value is IEnumerable enumerable && !(value is string)) { foreach (var item in enumerable) { TrimObjectProperties(item); } } else { TrimObjectProperties(value); } } } private static string SafeTrim(this string source) { return source?.Trim() ?? source; } }
修正点说明:
- 命名匹配:把方法名改成
QueryTrim,和你调用的_db.QueryTrim<dynamic>(...)对应,避免和Dapper原生Query方法冲突。 - dynamic类型处理:dynamic返回的是
ExpandoObject,需转换成字典来遍历修改属性值。 - 递归嵌套处理:不管是
Person里的Address这类嵌套对象,还是Payments集合,都会递归处理其中的字符串属性。 - 原地修改:直接修改原对象属性,避免额外内存开销。
二、其他更简洁的替代方案
如果不想写复杂的扩展方法,这些方案更省心:
1. 全局注册Dapper字符串类型处理器
一次性注册,所有Dapper查询的字符串字段都会自动修剪:
public class TrimmedStringTypeHandler : SqlMapper.TypeHandler<string> { public override void SetValue(IDbDataParameter parameter, string value) { parameter.Value = value?.Trim() ?? value; } public override string Parse(object value) { return value?.ToString().Trim() ?? null; } } // 程序启动时注册(比如Program.cs或Startup.cs) SqlMapper.AddTypeHandler(new TrimmedStringTypeHandler());
优点是一劳永逸,无需修改任何查询代码。
2. 在实体属性Setter中自动修剪
针对特定实体属性做修剪,灵活可控:
public class Address : BaseEntity { private string _street; public string Street { get => _street; set => _street = value.SafeTrim(); } private string _boxNumber; public string BoxNumber { get => _boxNumber; set => _boxNumber = value.SafeTrim(); } // 其他属性同理... } public static class StringExtensions { public static string SafeTrim(this string source) { return source?.Trim() ?? source; } }
3. 结合AutoMapper做修剪
如果已经用AutoMapper映射实体,可以全局配置字符串修剪规则:
var config = new MapperConfiguration(cfg => { // 全局规则:所有字符串映射自动修剪 cfg.ForAllMaps((map, exp) => { exp.ForAllMembers(opt => { if (opt.DestinationMember.PropertyType == typeof(string)) { opt.MapFrom(src => ((string)opt.SourceMember.GetValue(src)).SafeTrim()); } }); }); // 你的实体映射规则 cfg.CreateMap<dynamic, Identification>().ConvertUsing(IdentificationMapper.MapToIdentificationEntity); cfg.CreateMap<dynamic, Address>().ConvertUsing(AddressMapper.MapToAddressEntity); });
内容的提问来源于stack exchange,提问作者Gilles Vercammen
相关产品推荐
相关产品推荐

