【问题标题】:Automapper IEnumerable within class is not being mapped to RepeatedField类内的 Automapper IEnumerable 未映射到 RepeatedField
【发布时间】:2019-06-25 00:16:24
【问题描述】:

我想在两个类之间进行映射:

public class A {
    public IEnumerable<C> someList
}

public class B {
    public RepeatedField<D> someList
}

其中 RepeatedField 是来自 Google.Protobuf.Collections 的一个类,用于处理 gRPC 数据。

编辑:事实证明,gRPC 通过其原型创建类的方式与创建像 B 这样的类并不完全相同。请参阅我的答案。

我像这样创建一个 Automapper MappingConfiguration

return new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<C, D>().ReverseMap();
        cfg.CreateMap<A, B>().ReverseMap();
    });

然后通过 ASP.NET Startup 类注册。

如果我在另一个班级做这样的事情

A instanceA; // assume A's list has values inside
var listofD = this.mapper.Map<List<D>>(A.someList)

它正确地返回一个包含值的列表。然而:

A instanceA; // assume A's list has values inside
B instanceB = this.mapper.Map<B>(A);

返回B的一个实例,但instanceB里面的列表是空的。我该如何解决这个问题?

【问题讨论】:

    标签: c# list automapper ienumerable protobuf-csharp-port


    【解决方案1】:

    您需要创建一个custom type converter 来执行转换:

    private class EnumerableToRepeatedFieldTypeConverter<TITemSource, TITemDest> : ITypeConverter<IEnumerable<TITemSource>, RepeatedField<TITemDest>>
    {
        public RepeatedField<TITemDest> Convert(IEnumerable<TITemSource> source, RepeatedField<TITemDest> destination, ResolutionContext context)
        {
            destination = destination ?? new RepeatedField<TITemDest>();
            foreach (var item in source)
            {
                // obviously we haven't performed the mapping for the item yet
                // since AutoMapper didn't recognise the list conversion
                // so we need to map the item here and then add it to the new
                // collection
                destination.Add(context.Mapper.Map<TITemDest>(item));
            }
            return destination;
        }
    }
    

    如果需要,另一种方式:

    private class RepeatedFieldToListTypeConverter<TITemSource, TITemDest> : ITypeConverter<RepeatedField<TITemSource>, List<TITemDest>>
    {
        public List<TITemDest> Convert(RepeatedField<TITemSource> source, List<TITemDest> destination, ResolutionContext context)
        {
            destination = destination ?? new List<TITemDest>();
            foreach (var item in source)
            {
                destination.Add(context.Mapper.Map<TITemDest>(item));
            }
            return destination;
        }
    }
    

    你可以这样注册:

    ce.CreateMap(typeof(IEnumerable<>), typeof(RepeatedField<>)).ConvertUsing(typeof(EnumerableToRepeatedFieldTypeConverter<,>));
    ce.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListTypeConverter<,>));
    

    Try it online

    【讨论】:

    • 这个答案不起作用,目标 RepeatedFields 属性保持为空。在 AfterMap 步骤中复制属性有效。另见:github.com/AutoMapper/AutoMapper/issues/3157
    • @vivainio 我无法重现,即使使用最新的 AutoMapper 和 Google.Protobuf 包。我遇到的唯一问题是尝试将List&lt;string&gt; 映射到RepeatedField&lt;string&gt;,其中列表项之一是null,但这是RepeatedField&lt;string&gt; 对象的限制,而不是转换代码,我不想代表他人决定如何处理。您能否在 DotNetFiddle 上提供一个复制案例,以便我可以在需要时改进我的答案?
    • 我已经试过你的答案了。它适用于简单的对象。但是,我已经将类修改为具有内部列表的复杂对象。它不起作用。请查看link
    • 我的错,它有效。 try it
    【解决方案2】:

    我已经解决了这个问题。

    C# 类中的 Google.Protobuf.Collections.RepeatedField 是只读的,这意味着直接将值分配给它是行不通的,只会在返回时返回一个空列表。因此,我在两个较大的类之间创建了一个自定义类型转换器以将它们组合在一起。它所做的是将值直接添加到 RepeatedField,而不是填充我自己的 RepeatedField 并将值分配给类。

    public static class mapConfig
    {
        public static ContainerBuilder RegisterObjectMappers(this ContainerBuilder builder)
        {
            builder.Register(c => GetV1MapperConfiguration().CreateMapper())
                .As<IMapper>().SingleInstance();
    
            return builder;
        }
        private static MapperConfiguration GetMapConfig()
        {
            return new MapperConfiguration(cfg =>
            {
                // some mappings here
                cfg.CreateMap<C, D>().ReverseMap();
                cfg.CreateMap<A, B>().ConvertUsing<AToBConverter>();
            });
        }
    }
    public class AToBConverter : ITypeConverter<A, B>
    {
        public B Convert(A source, B destination, ResolutionContext context)
        {
            var b = new B
            {
                // internal values here aside from the repeated field(s)
            };
    
            // Need to use the Add method to add values rather than assign it with an '=' sign
            foreach (var someValue in source.someList)
            {
                b.someList.Add(context.Mapper.Map<D>(someValue));
            }
            return b;
        }
    }
    

    从 RepeatedField 转换为 List 或映射类中的任何其他 IEnumerable 没有任何问题,我不需要其他转换器。

    【讨论】:

    • 这就是上面的答案所做的,但以一种通用的方式。
    • 事实证明,如果您只是在处理一个只包含一个重复字段的类,那么另一个答案是有效的。然而,事实证明,由于 gRPC 的 proto 文件如何在类中生成重复字段,我的应用程序中的 B 类将所有重复字段设为只读。因此,除非我为存在重复字段的情况创建了一个特定的转换器,否则仅在两者之间进行转换对我不起作用。
    • 与为每种类型创建自己的 ITypeConverter 相比,实现分配所有 RepeatedField 属性的 AfterMap 更容易。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 2020-02-28
    • 1970-01-01
    • 2013-12-14
    • 2022-01-04
    相关资源
    最近更新 更多