Automapper - how to map to constructor parameters instead of property setters
Asked Answered
C

5

132

In cases where my destination setters are private, I might want to map to the object using the destination object's constructor. How would you do this using Automapper?

Cumulostratus answered 10/2, 2010 at 17:55 Comment(0)
A
175

Use ConstructUsing

this will allow you to specify which constructor to use during the mapping. but then all of the other properties will be automatically mapped according to the conventions.

Also note that this is different from ConvertUsing in that convert using will not continue to map via the conventions, it will instead give you full control of the mapping.

Mapper.CreateMap<ObjectFrom, ObjectTo>()
    .ConstructUsing(x => new ObjectTo(arg0, arg1, etc));

...

using AutoMapper;
using NUnit.Framework;

namespace UnitTests
{
    [TestFixture]
    public class Tester
    {
        [Test]
        public void Test_ConstructUsing()
        {
            Mapper.CreateMap<ObjectFrom, ObjectTo>()
                .ConstructUsing(x => new ObjectTo(x.Name));

            var from = new ObjectFrom { Name = "Jon", Age = 25 };

            ObjectTo to = Mapper.Map<ObjectFrom, ObjectTo>(from);

            Assert.That(to.Name, Is.EqualTo(from.Name));
            Assert.That(to.Age, Is.EqualTo(from.Age));
        }
    }

    public class ObjectFrom
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class ObjectTo
    {
        private readonly string _name;

        public ObjectTo(string name)
        {
            _name = name;
        }

        public string Name
        {
            get { return _name; }
        }

        public int Age { get; set; }
    }
}
Aileneaileron answered 10/2, 2010 at 19:21 Comment(11)
I'm guessing "ConstructUsing" must be in a newer version of automapper than the one we're using. Thanks JonCumulostratus
Much thanks for this example Jon. "ConstructUsing" is great! Allows me to keep my DTOs immutable with setters marked as private.Spar
Works a treat for me; AutoMapper currently doesn't like constructors where all parameters are optional, so I just use .ConstructUsing(x => new MyClass());Shay
How to do it with non-generic CreateMap method?Closed
Tried something similar Mapper.CreateMap<Order, OrderViewModel>().ConstructUsing(x => new OrderViewModel(this)); ... Get a "The call is ambiguos" compiler errorHannahannah
@ChrisKlepeis it's giving you that error because it doesn't know what "this" is referring to when attempting to map between the types. you want to do .ConstructUsing(x => new OrderViewModel(x)) most likely.Decoration
Both methods throw "Expression must be writeable Parameter name: left" if you use readonly fields instead of a private setters on your destination object.Consecution
What if I needed to pass something more sophisticated than a string? What if ObjectFrom contained ChildObjectFrom type property which had to be passed to ObjectTo constructor?Balakirev
Is this a better answer than @Matthieu's answer?Confidante
For anyone using this with EF Core - this worked in 2.2, but not in 3.0 due to a bug. If you are on 3.0 you will need to upgrade to 3.1.1 to get the fix - github.com/dotnet/efcore/issues/18888Importation
You meant probably that all other public properties not present in the constructor will be mapped according to convention. Private properties will not be mapped, so ConstructUsing can successfully be used with aggregates. Am I right?Viperous
G
17

The best practice is to use documented approaches from AutoMapper http://docs.automapper.org/en/stable/Construction.html

public class SourceDto
{
        public SourceDto(int valueParamSomeOtherName)
        {
            Value = valueParamSomeOtherName;
        }

        public int Value { get; }
}

Mapper.Initialize(cfg => cfg.CreateMap<Source, SourceDto>()
  .ForCtorParam(
    "valueParamSomeOtherName", 
    opt => opt.MapFrom(src => src.Value)
  )
);
Grishilda answered 6/8, 2017 at 17:9 Comment(2)
Unfortunately there’s no way to use nameof here to avoid brittle hard-coded magic-strings, and (unless AutoMapper now has a custom Roslyn rule) you won’t get a compile-time error if you omit a parameter or specify a parameter that doesn’t exist.Corridor
Maybe I misunderstand the situation, but I use nameof with my ForCtorParam like this: ```csharp CreateMap<Company, CompanyDto>() .ForCtorParam(nameof(CompanyDto.CountryCode), opt => opt.MapFrom(src => src.Country.Name)); CreateMap<OwnerCompany, OwnerCompanyDto>() .IncludeBase<Company, CompanyDto>() .ForCtorParam(nameof(CompanyDto.CountryCode), opt => opt.MapFrom(src => src.Country.Name)); Sorry about the formatting. I can't get formatting of code to work in comments any moreApteral
T
13

You should use the Map method that lets you set the destination. For example :

Mapper.CreateMap<ObjectFrom, ObjectTo>()

var from = new ObjectFrom { Name = "Jon", Age = 25 };

var to = Mapper.Map(from, new ObjectTo(param1));
Tita answered 28/2, 2014 at 19:22 Comment(0)
P
7

At the time of writing this answer, AutoMapper will do this automatically (with a simple CreateMap<>() call) for you if the properties match the constructor parameters. Of course, if things don't match up, then using .ConstructUsing(...) is the way to go.

public class PersonViewModel
{
    public int Id { get; set; }

    public string Name { get; set; }
}

public class Person
{
    public Person (int id, string name)
    {
        Id = id;
        Name = name;
    }

    public int Id { get; }

    public string Name { get; }
}

public class PersonProfile : Profile
{
    public PersonProfile()
    {
        CreateMap<PersonViewModel, Person>();
    }
}

Note: This assumes you are using Profiles to setup your automapper mappings.

When used like below, this produces the correct object:

var model = new PersonViewModel
{
    Id = 1
    Name = "John Smith"
}

// will correctly call the (id, name) constructor of Person
_mapper.Map<Person>(model);

You can read more about automapper construction in the offical wiki on GitHub

Pardue answered 4/5, 2017 at 14:39 Comment(1)
Looks like CreateMap<> should be PersonViewModel and not PersonProfile. As well as in the second code block PersonModel should be PersonViewModel.Saintly
C
5

Personally I always prefer to be as explicit as possible when using AutoMapper to avoid any potential bugs in the future.

If you call the ConstructUsing method just passing the parameters one by one in the good order you might face a bug one day.

What if a developer inverts 2 string parameters or add a new optional parameter before some existing optional parameters? You would get a mapping bug where a property isn't mapped to the destination property it's supposed to. For that reason I prefer to define my mapping using named parameters when instanciating my object.

Here are the different signatures of the ConstructUsing method:

TMappingExpression ConstructUsing(Func<TSource, ResolutionContext, TDestination> ctor);
TMappingExpression ConstructUsing(Expression<Func<TSource, TDestination>> ctor);

In that case we want to use the first one because it's not possible to use named parameters in an Expression Tree (you would get a compilation error an expression tree may not contain a named argument specification).

Here is how to use it:

 CreateMap<FromType, ToType>()
    .ConstructUsing((src, res) =>
    {
        return new ToType(
            foo: src.MyFoo,
            bar: res.Mapper.Map<BarModel>(src.MyBar),
        );
    });

Note the Func's 2nd parameter res which is the Resolution Context. This parameter allows you to use already registered mappings.

Careful though, I'd like to catch your attention on a drawback of declaring mappings with constructors. If your classes don't have public setters (read-only properties or private set) you won't be able to use the following overload of the Map method:

TDestination Map<TSource, TDestination>(TSource source, TDestination destination);

This overload could be very handy when updating an entity with EF Core for example

mapper.Map(updateModel, existingEntity);
await dbContext.SaveChangesAsync();

Thankfully there's another way to update entities with EF Core.

Carrack answered 27/8, 2020 at 10:19 Comment(2)
Reg "What if a developer inverts 2 string parameters" I have the understanding that the names of the parameter matters, not the order.Sechrist
Things I learned today: Having a mapper inside the ConstructUsing.Sechrist

© 2022 - 2024 — McMap. All rights reserved.