Automapper无法将类中的IEnumerable映射到RepeatedField。

5
我想要在两个类之间进行映射:
public class A {
    public IEnumerable<C> someList
}

并且

public class B {
    public RepeatedField<D> someList
}

RepeatedField是来自Google.Protobuf.Collections的一个处理gRPC数据的类。

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

我像这样创建Automapper MappingConfiguration:

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

然后它通过 ASP.NET 启动类进行注册。

如果我在另一个类中执行以下操作:

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类的一个实例,但是实例B内部的列表为空。我该如何解决这个问题?

2个回答

8
你需要创建一个自定义类型转换器来执行转换:
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<,>));

在线尝试


这个答案不起作用,目标 RepeatedFields 属性仍然为空。在 AfterMap 步骤中复制属性可以解决问题。另请参阅:https://github.com/AutoMapper/AutoMapper/issues/3157 - vivainio
@vivainio 我无法重现这个问题,即使使用最新的AutoMapper和Google.Protobuf包。我遇到的唯一问题是尝试将List<string>映射到RepeatedField<string>,其中一个列表项为null,但这是RepeatedField<string>对象的限制,而不是转换代码的限制,我不想代表其他人决定如何处理它。你能在DotNetFiddle上提供一个复现案例吗?这样我就可以改进我的答案。 - ProgrammingLlama
我已经尝试了你的答案。它适用于简单对象。但是,我已将类修改为具有内部列表的复杂对象。它不起作用。请检查链接:(https://dotnetfiddle.net/58Wy66) - Nguyễn Văn Biên
我的错误,它可以工作。试一下 - Nguyễn Văn Biên

-1

我已经解决了这个问题。

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并不麻烦,对我来说也不需要另一个转换器。


3
以上回答就是这样做的,但是以一种通用的方式表述。 - Lucian Bargaoanu
事实证明,如果你只是处理一个只包含RepeatedField的类,那么另一个答案是可行的。然而,由于gRPC的proto文件在类中生成重复字段,导致我的应用程序中的B类使所有重复字段都变为只读。因此,除非我为存在重复字段的情况创建一个特定的转换器,否则仅仅在两者之间进行转换对我来说是不起作用的。 - more whirlpools
1
不需要为每种类型创建自己的ITypeConverter,实现AfterMap并分配所有RepeatedField属性会更容易些。 - vivainio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接