ServiceStack.Text ConvertTo<>属性映射及多继承模型转换问题咨询
为什么customerData.Name为空?
你遇到的问题核心是**new关键字的属性隐藏特性**:
当你在AddCustomerInput中用new声明Name和PublicKey时,并没有重写基类CustomerData的属性,而是创建了完全独立的新属性。当你把AddCustomerInput实例传递给接收CustomerData参数的扩展方法时,通过customerData.Name访问的是基类CustomerData的Name属性——而这个属性从来没有被赋值过(你赋值的是子类的new Name),所以自然为空。
简单来说:基类引用只能访问基类的成员,子类用new隐藏的成员对基类引用是不可见的。
能否用ServiceStack.Text的ConvertTo<>实现属性名映射?
可以!ServiceStack.Text支持通过特性配置属性映射,不需要手动赋值:
方法:使用[Alias]特性
在你的类上标记[Alias]特性,指定目标类型的属性名:
public class CustomerData { [Alias("CustomerName")] // 标记映射到ExternalCustomerData的CustomerName public string Name { get; set; } public string PublicKey { get; set; } } public class ExternalCustomerData { public string CustomerName{ get; set; } public string PublicKey { get; set; } }
这样调用customerData.ConvertTo<ExternalCustomerData>()时,ServiceStack.Text会自动把Name映射到CustomerName,不需要手动写赋值那一行。
注意:解决属性隐藏的坑
因为你用new隐藏了基类属性,子类AddCustomerInput的Name并不会被基类的[Alias]影响。如果要让子类的属性也参与映射,你需要在子类的new属性上也标记[Alias],或者更合理的做法是避免用new隐藏属性——比如把CustomerData设计为抽象类,子类通过override重写属性(基类属性需设为virtual),或者调整DTO的继承结构,让基类只包含通用属性,子类添加额外属性而非隐藏原有属性。
多继承模型的通用转换方案(无需为每个子类写扩展)
如果不想为每个子类单独写扩展方法,推荐以下两种通用方案:
方案1:使用AutoMapper(最推荐)
AutoMapper专门处理对象映射,支持继承关系,只需要一次配置就能处理所有子类:
- 安装AutoMapper NuGet包
- 创建映射配置:
var configuration = new MapperConfiguration(cfg => { // 配置基类到ExternalCustomerData的映射 cfg.CreateMap<CustomerData, ExternalCustomerData>() .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Name)); // AutoMapper会自动处理子类继承,不需要单独配置AddCustomerInput }); IMapper mapper = configuration.CreateMapper();
- 使用时直接调用:
AddCustomerInput input = new AddCustomerInput { Name = "Test", PublicKey = "123" }; ExternalCustomerData external = mapper.Map<ExternalCustomerData>(input);
AutoMapper会自动识别实例的实际类型(AddCustomerInput),并正确获取它的Name属性值,不会出现空值问题。
方案2:封装通用反射转换方法
如果不想引入第三方库,可以自己写一个通用转换方法,通过反射获取实例的实际类型的属性值(而非基类的):
public static TTarget ConvertTo<TTarget>(this object source) where TTarget : new() { if (source == null) return default; TTarget target = new TTarget(); var sourceProps = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); var targetProps = typeof(TTarget).GetProperties(BindingFlags.Public | BindingFlags.Instance); // 定义属性映射规则(可提取到独立配置类中维护) var propertyMappings = new Dictionary<string, string> { { "Name", "CustomerName" } }; foreach (var sourceProp in sourceProps) { string targetPropName = propertyMappings.TryGetValue(sourceProp.Name, out var mappedName) ? mappedName : sourceProp.Name; var targetProp = targetProps.FirstOrDefault(p => p.Name.Equals(targetPropName, StringComparison.OrdinalIgnoreCase)); if (targetProp != null && targetProp.CanWrite) { var value = sourceProp.GetValue(source); targetProp.SetValue(target, value); } } return target; }
使用时直接调用:
AddCustomerInput input = new AddCustomerInput { Name = "Test", PublicKey = "123" }; ExternalCustomerData external = input.ConvertTo<ExternalCustomerData>();
这个方法会获取input的实际类型AddCustomerInput的属性,所以能正确拿到Name的值,同时支持自定义属性映射。
内容的提问来源于stack exchange,提问作者someone

