如何使用CsvHelper实现嵌套类C的通用映射?避免重复编码
问题需求
现有类结构如下:
class A { public C MyC; public int OtherStuff; } class B { public C MyC1; public C MyC2; } class C { public int WeirdInt; public string WeirdString; }
需要为类C定义一套通用的CSV映射规则,在转换List<A>或List<B>到CSV时自动应用,避免重复编写C类的映射代码。
期望输出的CSV表头:
- 转换
List<A>时:MyC_WeirdInt, MyC_WeirdString, OtherStuff - 转换
List<B>时:MyC1_WeirdInt, MyC1_WeirdString, MyC2_WeirdInt, MyC2_WeirdString
当前实现方式是分别编写AMap和BMap,但存在大量重复的C类映射代码:
class AMap : ClassMap<A> { public AMap() { Map(m => m.MyC.WeirdInt).Name("MyC_WeirdInt"); Map(m => m.MyC.WeirdString).Name("MyC_WeirdString"); Map(m => m.OtherStuff); } } class BMap : ClassMap<B> { public BMap() { Map(m => m.MyC1.WeirdInt).Name("MyC1_WeirdInt"); Map(m => m.MyC1.WeirdString).Name("MyC1_WeirdString"); Map(m => m.MyC2.WeirdInt).Name("MyC2_WeirdInt"); Map(m => m.MyC2.WeirdString).Name("MyC2_WeirdString"); } }
解决方案
可以通过扩展方法封装C类的通用映射逻辑,在各个ClassMap中直接调用,彻底消除重复代码。
步骤1:封装C类的通用映射扩展
public static class CMapExtensions { public static void MapC<T>(this ClassMap<T> map, Expression<Func<T, C>> propertyAccessor) { // 提取属性名称作为表头前缀 var memberExpr = (MemberExpression)propertyAccessor.Body; var propertyName = memberExpr.Member.Name; // 统一映射C类的两个字段 map.Map(m => propertyAccessor.Compile()(m).WeirdInt) .Name($"{propertyName}_WeirdInt"); map.Map(m => propertyAccessor.Compile()(m).WeirdString) .Name($"{propertyName}_WeirdString"); } }
步骤2:简化原有映射类的实现
现在只需要在AMap和BMap中调用扩展方法即可,无需重复写C类的映射代码:
class AMap : ClassMap<A> { public AMap() { this.MapC(m => m.MyC); // 自动用MyC作为前缀 Map(m => m.OtherStuff); } } class BMap : ClassMap<B> { public BMap() { this.MapC(m => m.MyC1); // 自动用MyC1作为前缀 this.MapC(m => m.MyC2); // 自动用MyC2作为前缀 } }
进阶:自动识别所有C类型属性(可选)
如果想更省心,不需要手动逐个调用MapC,可以通过反射自动遍历类中所有C类型的属性并添加映射:
public static void MapAllCProperties<T>(this ClassMap<T> map) { // 反射获取当前类中所有类型为C的属性 var cProperties = typeof(T).GetProperties() .Where(p => p.PropertyType == typeof(C)); foreach (var prop in cProperties) { // 构建属性访问的表达式树 var param = Expression.Parameter(typeof(T), "m"); var propAccess = Expression.Property(param, prop); var lambda = Expression.Lambda<Func<T, C>>(propAccess, param); map.MapC(lambda); } }
此时映射类可以进一步简化成:
class AMap : ClassMap<A> { public AMap() { this.MapAllCProperties(); // 自动处理所有C类型属性 Map(m => m.OtherStuff); } } class BMap : ClassMap<B> { public BMap() { this.MapAllCProperties(); // 自动处理所有C类型属性 } }
这样不管类中有多少个C类型的属性,都能自动套用统一的映射规则,完全避免重复代码。
内容的提问来源于stack exchange,提问作者狸子Neazle
相关产品推荐
相关产品推荐

