17
votes

It's not so much a question, as I have found a way to do what I want, but it seems like there should be a better way to do it. I've searched everywhere and not found anything.

Basically, I have what I consider a very standard object model.

public class Parent
{
    private readonly IList<Child> _children = new List<Child>();
    public IEnumerable<Child> Children { get { return _children; } }
    public void AddChild(Child child)
    {
        child.Parent = this;
        _children.Add(child);
    }
}

and

public class Child
{
    public Parent Parent { get; set; }
}

(I've omitted properties irrelevant to the problem and error checking and guarding for the sake of clarity...)

public class ParentDTO
{
    List<ChildDTO> Children = new List<ChildDTO>();
}

and

public class ChildDTO
{
}

The reason I'm using a method to add children to the collection is to maintain control of business logic that needs to be processed when a child is added.

With the standard mapping of:

Mapper.CreateMap<Parent, ParentDTO>();
Mapper.CreateMap<ParentDTO, Parent>();
Mapper.CreateMap<Child, ChildDTO>();
Mapper.CreateMap<ChildDTO, Child>();

It seems to work fine coming from the service layer. The children of the domain object map perfectly to the list of ChildDTO instances.

However, when mapping back the other way, the collection on the domain model isn't set - because it's readonly, obviously. There doesn't appear to be any way to directly set the private field using AutoMapper. I have tried various suggestions found on here, and other parts of the internet.

In the end, I came up with the following mapping:

Mapper.CreateMap<ParentDTO, Parent>()
    .ForMember(m => m.Children, o => o.Ignore())
    .AfterMap((s, d) =>
                        {
                            foreach(var c in s.Children)
                                d.AddChild(Mapper.Map<ChildDTO, Child>(c));
                        });

This works as I required. However, I can't help feeling that there has to be a better way, and I haven't tested this with an existing parent that's had children modified and maybe added and removed, so I know it's not actually correct yet. Ultimately, this domain model is persisted using NHibernate, so I do have to worry about that. But, one thing at a time. :)

Hopefully this might help someone else who's encountering the same problem, and maybe someone who's solved it properly will be able to correct me.

2
+1 your aftermap solution saved me.Chris Marisic
Cool solution. I'll borrow it :-)LeftyX
Great idea with AfterMap. It's so much easier to work with Source and Destination objects than with ResolutionResult and stuff like that!Andrei

2 Answers

3
votes

I don't like to have logic inside property getter or setter, but what about adding setter to Children property of Parent class

public class Parent
{
    private readonly IList<Child> _children = new List<Child>();

    public IEnumerable<Child> Children
    {
        get => _children;
        set => AddChildren(value);
    }

    public void AddChild(Child child)
    {
        child.Parent = this;
        _children.Add(child);
    }

    private void AddChildren(IEnumerable<Child> children)
    {
        _children.Clear();

        foreach (var child in children)
        {
            AddChild(child);
        }
    }
}

And AfterMap logic is not more necessary. Mapping configuration will be simple

Mapper.CreateMap<ParentDTO, Parent>();
0
votes

I think if you are going to protect properties for good business logic reasons then it would be bad if AutoMapper circumvented them when doing its mapping. In situations like this I prefer to abandon the fluent syntax and place the creation logic in its own method like this:

private Parent MapParentDTOToParent(ParentDTO source)
{
    var parent = new Parent();
    // Business logic here
    return parent
}

and then:

Mapper.CreateMap<ParentDTO, Parent>().ConvertUsing(MapParentDTOToParent);

I find this easier to follow than having lots of ignore declarations.