寻求跨数据源实体映射的优雅泛型实现方案
优雅实现泛型数据映射的解决方案
针对你遇到的泛型方法中调用重载映射方法的问题,这里提供几种轻量且符合开闭原则的解决方案,替代硬编码判断或笨重的设计模式:
方案一:委托字典注册映射(推荐,无侵入+高性能)
通过静态字典注册每个TA到TB的映射委托,避免反射开销,同时保持扩展性:
public static class Mapper { private static readonly Dictionary<Type, Delegate> _typeMappings = new Dictionary<Type, Delegate>(); // 静态构造函数初始化所有映射关系 static Mapper() { Register<ClassA1, ClassB1>(a => new ClassB1()); Register<ClassA2, ClassB2>(a => new ClassB2()); // 新增类型对时,在此添加一行Register即可 } // 对外暴露的注册方法(如需动态添加) public static void Register<TA, TB>(Func<TA, TB> mappingFunc) { _typeMappings[typeof(TA)] = mappingFunc; } // 单个实体映射 public static TB Map<TA, TB>(TA source) { if (_typeMappings.TryGetValue(typeof(TA), out var del) && del is Func<TA, TB> mapper) return mapper(source); throw new InvalidOperationException($"未找到{typeof(TA)}到{typeof(TB)}的映射关系"); } // 批量List映射 public static List<TB> Map<TA, TB>(List<TA> sourceList) { return sourceList.Select(Map<TA, TB>).ToList(); } }
此时你的ProcessEntity方法可以直接正常调用:
public void ProcessEntity<TA, TB>(string EntityNameInSourceA, string EntityNameInSourceB) { List<TA> dataA = Source1.GetData<TA>(EntityNameInSourceA); List<TB> dataB = Mapper.Map<TA, TB>(dataA); Source2.SaveData<TB>(dataB); }
新增类型对时,只需在Mapper的静态构造函数中添加一行Register<ClassA4, ClassB4>(a => new ClassB4());,再调用ProcessEntity<ClassA4, ClassB4>("TableA4", "TableB4");即可,完全符合你的需求。
方案二:扩展泛型映射方法(兼容原有重载)
如果你想保留原有的重载Map方法,可通过反射封装泛型批量映射(注意:反射有一定性能损耗,适合性能要求不高的场景):
public static class Mapper { // 保留原有重载方法 public static ClassB1 Map(ClassA1 source) => new ClassB1(); public static ClassB2 Map(ClassA2 source) => new ClassB2(); // ... // 缓存映射方法,避免每次反射查找 private static readonly Dictionary<Type, MethodInfo> _methodCache = new Dictionary<Type, MethodInfo>(); public static List<TB> Map<TA, TB>(List<TA> sourceList) { var sourceType = typeof(TA); if (!_methodCache.TryGetValue(sourceType, out var mapMethod)) { mapMethod = typeof(Mapper).GetMethod("Map", new[] { sourceType }); if (mapMethod == null) throw new InvalidOperationException($"未找到{sourceType}对应的映射方法"); _methodCache[sourceType] = mapMethod; } return sourceList.Select(item => (TB)mapMethod.Invoke(null, new[] { item })).ToList(); } }
方案三:接口约束(侵入式但最简洁)
如果你有权修改SourceA的实体类,可以让它们实现统一的映射接口,彻底摆脱静态Mapper类:
// 定义映射接口 public interface IMappable<TB> { TB MapTo(); } // 让SourceA的实体实现接口 public class ClassA1 : IMappable<ClassB1> { public ClassB1 MapTo() => new ClassB1(); } public class ClassA2 : IMappable<ClassB2> { public ClassB2 MapTo() => new ClassB2(); } // 泛型批量映射扩展 public static class MappingExtensions { public static List<TB> MapAll<TA, TB>(this List<TA> sourceList) where TA : IMappable<TB> { return sourceList.Select(a => a.MapTo()).ToList(); } }
此时ProcessEntity方法可简化为:
public void ProcessEntity<TA, TB>(string EntityNameInSourceA, string EntityNameInSourceB) where TA : IMappable<TB> { List<TA> dataA = Source1.GetData<TA>(EntityNameInSourceA); List<TB> dataB = dataA.MapAll<TA, TB>(); Source2.SaveData<TB>(dataB); }
方案对比
- AutoMapper:成熟第三方库,无需手动编写映射逻辑,但会引入外部依赖,适合复杂映射场景。
- 硬编码if判断:扩展性极差,新增类型需修改判断逻辑,违反开闭原则,不推荐。
- 适配器模式:过于笨重,为每个类型对编写适配器类会导致类爆炸,不适用于此场景。
内容的提问来源于stack exchange,提问作者Rob
相关产品推荐
相关产品推荐

