I have a self referencing model that I would like to convert to a flat list. The model looks like this.
public class Node
{
public List<Node> Nodes { get; set; }
public Person Person { get; set; }
public Language Language { get; set; }
}
public class NodeDTO
{
public PersonDTO Person { get; set; }
public LanguageDTO Language { get; set; }
}
public class NodeListDTO
{
public List<NodeDTO> Nodes { get; set; }
}
I want all nodes in the hierarchy to be flattend to one single list in my DTO object. Is this possible with Auto Mapper.
I have tried to use a Custom Value resolver but I haven't figured out how to use the mappings for PersonDTO and LanguageDTO inside the value resolver.
First of all, you will need code to flatten nodes hierarchy. AutoMapper will not do that automatically. I use following extension methods for that:
public static IEnumerable<T> Flatten<T>(this T root,
Func<T, IEnumerable<T>> childrenSelector)
{
if (!typeof(T).IsValueType && root == null)
throw new ArgumentNullException(nameof(root));
return Flatten(new[] { root }, childrenSelector);
}
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> source,
Func<T, IEnumerable<T>> childrenSelector)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (childrenSelector == null)
throw new ArgumentNullException(nameof(childrenSelector));
return FlattenIterator(source, childrenSelector);
}
private static IEnumerable<T> FlattenIterator<T>(this IEnumerable<T> source,
Func<T, IEnumerable<T>> childrenSelector)
{
if (source != null)
{
foreach (var item in source)
{
yield return item;
var children = childrenSelector(item);
if (children != null)
foreach (var child in FlattenIterator(children, childrenSelector))
yield return child;
}
}
}
Next - you need to tell AutoMapper how to flatten and map nodes hierarchy to list
MapperConfiguration config = new MapperConfiguration(c =>
{
c.CreateMap<Person, PersonDTO>();
c.CreateMap<Language, LanguageDTO>();
c.CreateMap<Node, NodeDTO>();
c.CreateMap<Node, NodeListDTO>()
.ForMember(d => d.Nodes, o => o.ResolveUsing(s => s.Flatten(n => n.Nodes)));
});
And now you can simply use
var node = new Node {
Person = new Person { Id = 1, Name = "Bob" },
Language = new Language { Id = 10, Code = "en" },
Nodes = new List<Node> {
new Node {
Person = new Person { Id = 3, Name = "Mike"},
Language = new Language { Id = 11, Code = "es"},
Nodes = new List<Node> {
new Node {
Person = new Person { Id = 4, Name = "Alex"},
Language = new Language { Id = 11, Code = "es"}
}
}
},
new Node {
Person = new Person { Id = 5, Name = "Serge"},
Language = new Language { Id = 12, Code = "by"}
}
}
};
var nodeListDTO = mapper.Map<NodeListDTO>(node);
Result:
{
"Nodes": [
{
"Person": { "Id": 1, "Name": "Bob" },
"Language": { "Id": 10, "Code": "en" }
},
{
"Person": { "Id": 3, "Name": "Mike" },
"Language": { "Id": 11, "Code": "es" }
},
{
"Person": { "Id": 4, "Name": "Alex" },
"Language": { "Id": 11, "Code": "es" }
},
{
"Person": { "Id": 5, "Name": "Serge" },
"Language": { "Id": 12, "Code": "by" }
}
]
}