问题
I want to map between two classes:
public class A {
public IEnumerable<C> someList
}
and
public class B {
public RepeatedField<D> someList
}
where RepeatedField is a class from Google.Protobuf.Collections that handles gRPC data.
EDIT: As it turns out, the way that gRPC creates classes via its prototype is not exactly like creating a class like B. See my answer.
I create an Automapper MappingConfiguration like this
return new MapperConfiguration(cfg =>
{
cfg.CreateMap<C, D>().ReverseMap();
cfg.CreateMap<A, B>().ReverseMap();
});
and then it gets registered via ASP.NET Startup class.
If I do something like this in another class
A instanceA; // assume A's list has values inside
var listofD = this.mapper.Map<List<D>>(A.someList)
it correctly returns a list with values inside. However:
A instanceA; // assume A's list has values inside
B instanceB = this.mapper.Map<B>(A);
returns an instance of B, but the list inside of instanceB is empty. How do I fix this?
回答1:
You need to create a custom type converter for performing the conversion:
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;
}
}
And the other way, if required:
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;
}
}
Which you can register like so:
ce.CreateMap(typeof(IEnumerable<>), typeof(RepeatedField<>)).ConvertUsing(typeof(EnumerableToRepeatedFieldTypeConverter<,>));
ce.CreateMap(typeof(RepeatedField<>), typeof(List<>)).ConvertUsing(typeof(RepeatedFieldToListTypeConverter<,>));
Try it online
回答2:
I've solved the issue.
A Google.Protobuf.Collections.RepeatedField inside a C# class is readonly, meaning that directly assigning values into it won't work and will only return an empty list on the way back. Therefore, I created a custom type converter between the two larger classes to bring them together. What it does is add values directly into the RepeatedField rather than populating my own RepeatedField and assigning the value into the class.
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;
}
}
Converting from RepeatedField to a List or any other IEnumerable in a mapped class isn't any trouble and didn't require another converter for me.
来源:https://stackoverflow.com/questions/56745327/automapper-ienumerable-within-class-is-not-being-mapped-to-repeatedfield