Use automapper to map nested collections of objects - c#

I found this link on Github which shows how to set up a mapper configuration when an inner map is nested within an outer one.
https://github.com/AutoMapper/AutoMapper/wiki/Nested-mappings
However this isn't working for me probably as I have a bit of a difference: My Inner object is actually a Collection of InnerSource,
i.e.
public class OuterSource
{
public int Value { get; set; }
public Collection<InnerSource> Inners { get; set; }
}
public class InnerSource
{
public int OtherValue { get; set; }
}
and
public class OuterDest
{
public int Value { get; set; }
public Collection<InnerDest> Inners { get; set; }
}
public class InnerDest
{
public int OtherValue { get; set; }
}
The solution in the link gives this code to create the MapperConfiguration
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<OuterSource, OuterDest>();
cfg.CreateMap<InnerSource, InnerDest>();
});
// setup object data
var mapper = config.CreateMapper();
var dest = mapper.Map<OuterSource, OuterDest>(source);
But then when I run this, I get an AutoMapperMappingException
(Inner Exception =
Argument 'value' must not be null.
Parameter name: value )
How can I create a nested mapping like this but where the inner object is a Collection? Note my real case is a bit more complex than the code provided above (with several .ForMember() calls)

Related

Automapper along with generics and mapping missing properties

I'm trying to use a generic mapper for mapping two objects. So I have setup Automapper in this way which comes from the documentation:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Source<>), typeof(Destination<>));
});
mapper = config.CreateMapper();
Now everything works well in the case if the source and destination have the same properties and if I'm going from more properties on the source side to less properties on the destination. However if the source has less properties than the destination than I get an error:
Value ---> AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
My question is there a way I could ignore these properties even though I won't know what they are at compile time?
Source:
public class Source<T>
{
public T Value { get; set; }
}
public class Destination<T>
{
public T Value { get; set; }
}
public class DataObject1
{
public int Id { get; set; }
public string Code { get; set; }
}
public class DataObject2
{
public int Id { get; set; }
public string Code { get; set; }
public string ActiveFlag { get; set; }
}
Here is my Test Code:
var data = new DataObject1() { Id = 10, Code = "Test" };
var source = new Source<DataObject1> { Value = data };
var dest = mapper.Map<Source<DataObject1>, Destination<DataObject2>>(source);
Assert.AreEqual(dest.Value.Id, 10);
Your mapping will succeed if you map DataObject1 to DataObject2 when you configure your mapper like so:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap(typeof(Source<>), typeof(Destination<>));
cfg.CreateMap<DataObject1, DataObject2>();
});
... or are you trying to avoid having to know at compile time that you may need to map DataObject1 to DataObject2 at all?

automapper working with attributes c#

I have two objects, I want to map them using AutoMapper Attributes, these are my target objects:
public class ClaseB
{
public string UBLVersionID_nuevo { get; set; }
public ClaseB_inside objetoB_inside { get; set; }
}
public class ClaseB_inside
{
public string texto_inside { get; set; }
}
and this is my source class:
[MapsTo(typeof(ClaseB))]
public class ClaseA
{
[MapsToProperty(typeof(ClaseB), "objetoB_inside.texto_inside")]
public string texto { get; set; } = "texto prueba";
[MapsToProperty(typeof(ClaseB), "UBLVersionID_nuevo")]
public string texto2 { get; set; } = "texto 2 de prueba";
}
when I try to map I get the following error:
Error mapping types
and with this change:
[MapsTo(typeof(ClaseB))]
public class ClaseA
{
[MapsToProperty(typeof(ClaseB_inside), "objetoB_inside.texto_inside")]
public string texto { get; set; } = "texto prueba";
[MapsToProperty(typeof(ClaseB), "UBLVersionID_nuevo")]
public string texto2 { get; set; } = "texto 2 de prueba";
}
I get null in ClaseB.objetoB_inside but ClaseB.UBLVersionID_nuevo it works.
What am I doing wrong?
I think the issue is with the way you are defining the mapping. Consider the following if you weren't using Automapper attributes and was initializing through the static API:
Mapper.Initialize(expression =>
{
expression.CreateMap<ClaseA, ClaseB>()
.ForMember(
from => from.objetoB_inside.texto_inside,
to => to.MapFrom(a => a.texto2));
});
This mapping would result in the following exception:
Expression 'from => from.objetoB_inside.texto_inside' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead.
And I think that's the same issue with the Attributes definition.
So I would suggest implementing the following:
public class MapsToClaseB : MapsToAttribute
{
public MapsToClaseB() : base(typeof(ClaseB)) { }
public void ConfigureMapping(IMappingExpression<ClaseA, ClaseB> mappingExpression)
{
mappingExpression.AfterMap(
(a, b) => b.objetoB_inside = new ClaseB_inside{texto_inside = a.texto});
}
}
You just then need to decorate your class with this:
[MapsToClaseB]

How can I flatten a nested class when using an instance of (not static) AutoMapper?

I'm trying to use AutoMapper (v6.1.1) to flatten out a class containing further nested classes.
For reasons™ I'm unable to alter these classes, so altering names is not possible.
Bearing this in mind I can solve it using the static Mapper like so:
// This is the nested user, it has a further nested class
public class NestedUser
{
public long id { get; set; }
public string type { get; set; }
public Attributes attributes { get; set; }
}
public class Attributes
{
public string first_name { get; set; }
public string last_name { get; set; }
public string name { get; set; }
}
// This is the flattened representation
public class FlattenedUser
{
public long id { get; set; }
public string type { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public string name { get; set; }
}
// Create a nested user
var nested = new NestedUser
{
id = 1,
type = "Contact",
attributes = new Attributes
{
first_name = "Equals",
last_name = "Kay",
name = "Equalsk"
}
};
// Use the static Mapper to flatten
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
Mapper.AssertConfigurationIsValid();
var flattened = Mapper.Map<FlattenedUser>(nested);
The object flattened now has all of its properties correctly populated.
For more reasons™ I want to use an instance of AutoMapper, something like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
config.AssertConfigurationIsValid();
var flattened = config.CreateMapper().Map<FlattenedUser>(nested);
My issue is the line .ConstructUsing(s => ... ));, it refers to the static Mapper and so it throws a runtime exception:
System.InvalidOperationException: 'Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.'
I don't want to use .ForMember(...) for each nested property as it defeats the object of what I'm trying to do.
Now I'm stuck. Is it possible to flatten out the nested class using an instance of AutoMapper as opposed to the static way?
I see 2 options to resolve the issue, depending on your case one should fit:
First: if you want to continue using static mapper for nested object than you have to move registration of nested objects to static configuration:
Mapper.Initialize(cfg=> cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None));
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing(s => Mapper.Map<FlattenedUser>(s.attributes));
});
Second: Use ResolutionContext
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Attributes, FlattenedUser>(MemberList.None);
cfg.CreateMap<NestedUser, FlattenedUser>(MemberList.None)
.ConstructUsing((s, r) => r.Mapper.Map<FlattenedUser>(s.attributes));
});

How to map some source properties to a wrapped destination type using AutoMapper?

Suppose you have this source model:
public abstract class SourceModelBase {
}
public class SourceContact : SourceModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; }
public SourceAddress Address { get; set; }
}
public class KeyValuePair { // Not derived from SourceModelBase.
public string Key { get; set; }
public string Value { get; set; }
}
public class SourceAddress : SourceModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Now the destination model should be mapped 1:1 by default (subject to normal AutoMapper configuration), but each thing derived from SourceModelBase should be mapped to a wrapper class class Wrap<T> { T Payload { get; set; } string Meta { get; set; } }.
public abstract class DestinationModelBase {
}
public class DestinationContact : DestinationModelBase {
public string FirstName { get; set; }
public string LastName { get; set; }
public KeyValuePair Pair { get; set; } // Not wrapped, base class not `SourceModelBase`.
public Wrap<DestinationAddress> Address { get; set; }
}
public class DestinationAddress : DestinationModelBase {
public string StreetName { get; set; }
public string StreetNumber { get; set; }
}
Since the contact class itself is derived from SourceModelBase it should be wrapped as well.
The result should have this structure:
Wrap<DestinationContact> Contact
string Meta // Comes from the custom wrapper logic.
DestinationContact Payload
string FirstName
string LastName
KeyValuePair Pair
string Key
string Value
Wrap<DestinationAddress> Address
string Meta // Comes from the custom wrapper logic.
DestinationAddress Payload
string StreetName
string StreetNumber
Obviously this wrapping should nest, illustrated by the fact that the mapped object itself is subject to it and so is its Address property.
For some reason all I keep finding are questions related to mapping from destination to source. I know I have to somehow use ResolveUsing and if the destination type is derived from SourceModelBase, somehow apply custom logic to provide the Wrap<T> value based on the value of the source property.
I don't know where to start at all, though. Especially when the source object itself is specified to be subject of the wrapping logic as well.
What's the best, most AutoMapper-idiomatic way to wrap the nested objects if they meet a condition and at the same time wrap the original object as well if it meets the same condition? I already have the mapper creation abstracted away so I can mold the original object automatically before passing it to the mapper, which may help with subjecting the original object to the resolver as well by doing mapper.Map(new { Root = originalObject }) so the resolver sees the instance of the original object as if it was a value of a property of source object as well, not the source object itself.
According to this issue on AutoMapper GitHub page, there is no direct way to do it.
But there is some workarounds. For example - reflection.
In this case you need to know wrapper type and implement converter for desired types. In this example it's MapAndWrapConverter from TSource to Wrap<TDestination>
CreateWrapMap method creates two bindings:
SourceAddress -> Wrap<DestinationAddress> and SourceContact -> Wrap<DestinationContact> which allow you to map SourceContant to wrapped DestinationContact.
internal class Program
{
public static void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceAddress, DestinationAddress>();
cfg.CreateMap<SourceContact, DestinationContact>();
cfg.CreateWrapMap(
//func selecting types to wrap
type => typeof(DestinationModelBase).IsAssignableFrom(type)
&& !type.IsAbstract,
typeof(Wrap<>),
typeof(MapAndWrapConverter<,>));
});
var mapper = config.CreateMapper();
//Using AutoFixture to create complex object
var fixture = new Fixture();
var srcObj = fixture.Create<SourceContact>();
var dstObj = mapper.Map<Wrap<DestinationContact>>(srcObj);
}
}
public static class AutoMapperEx
{
public static IMapperConfigurationExpression CreateWrapMap(
this IMapperConfigurationExpression cfg,
Func<Type, bool> needWrap, Type wrapperGenericType,
Type converterGenericType)
{
var mapperConfiguration =
new MapperConfiguration((MapperConfigurationExpression)cfg);
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (var dstType in types.Where(needWrap))
{
var srcType = mapperConfiguration.GetAllTypeMaps()
.Single(map => map.DestinationType == dstType).SourceType;
var wrapperDstType = wrapperGenericType.MakeGenericType(dstType);
var converterType = converterGenericType.MakeGenericType(srcType, dstType);
cfg.CreateMap(srcType, wrapperDstType)
.ConvertUsing(converterType);
}
return cfg;
}
}
public class MapAndWrapConverter<TSource, TDestination>
: ITypeConverter<TSource, Wrap<TDestination>>
{
public Wrap<TDestination> Convert(
TSource source, Wrap<TDestination> destination, ResolutionContext context)
{
return new Wrap<TDestination>
{
Payload = context.Mapper.Map<TDestination>(source)
};
}
}
CreateWrapMap method is a little bit messy, especially the part with finding matching types. But it can be refined according to your needs.

AutoMapping objects

I have a weird situation where I have objects and Lists of objects as part of my entities and contracts to interface with a third-party service. I'm going to try to see if I can replace the actual object class with something more specific in the entities and contracts to get around this, but I am curious if there is a way to get AutoMapper to handle this as is.
Here are some dummy classes:
public class From
{
public object Item { get; set; }
}
public class FromObject
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
And the quick replication:
Mapper.CreateMap<From, To>();
Mapper.CreateMap<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // FromObject
Basically, the question is this: Is there a way to get AutoMapper to understand that from.Item is a FromObject and apply the mapping to ToObject? I'm thinking there's probably not a way to make it automatic, since there's nothing that would indicate that to.Item has to be a ToObject, but is there a way to specify during the CreateMap or Map calls that this should be taken into account?
I don't think there is an "automatic" way of doing it, since AutoMapper won't be able to figure out that From.Item is FromObject and To.Item is ToObject.
But, while creating mapping, you can specify that
Mapper.CreateMap<FromObject, ToObject>();
Mapper.CreateMap<From, To>()
.ForMember(dest => dest.Item, opt => opt.MapFrom(src => Mapper.Map<ToObject>(src.Item)));
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject
If you're willing to use an additional interface, this can be accomplished using Include. You can't just map object to object in this fashion, though.
public class From
{
public IItem Item { get; set; }
}
public class FromObject : IItem
{
public string Value { get; set; }
}
public class To
{
public object Item { get; set; }
}
public class ToObject
{
public string Value { get; set; }
}
public interface IItem
{
// Nothing; just for grouping.
}
Mapper.CreateMap<From, To>();
Mapper.CreateMap<IItem, object>()
.Include<FromObject, ToObject>();
From from = new From { Item = new FromObject { Value = "Test" } };
To to = Mapper.Map<To>(from);
string type = to.Item.GetType().Name; // ToObject

Categories

Resources