Map one C# Record to another with AutoMapper

Records were introduced in C# 9.0 and are a handy way of holding data, instead of using properties of classes. I recently came along the task of mapping one record of one type to one of another type by using AutoMapper. At first I thought I will map them exactly as I would map the properties of two different classes. However, it was trickier than that :)

The ForMember() method will not work, since we get the error No available constructor. We will have to use the ForCtorParam() method for that. Suppose we have a Record of type PersonModel and a Record of type PersonEntity, with both having two parameters, the FirstName and the LastName. We would declare the PersonModel like this:

1
    public record PersonModel(string FirstName, string LastName);

And the mapping between the two Records would be like this:

1
2
3
    CreateMap<PersonModel, PersonEntity>()
        .ForCtorParam(nameof(PersonEntity.FirstName), options => options.MapFrom(source => source.FirstName))
        .ForCtorParam(nameof(PersonEntity.LastName), options => options.MapFrom(source => source.LastName));

The first parameter of ForCtorParam() is the string name of the parameter, the second a lambda of what we want to map to this parameter.

We are not ready yet. If we run a unit-test which check if the mappings of our application are valid, by using the MapperConfiguration.AssertConfigurationIsValid() we are going to get the Unmapped Properties: FirstName, LastName error. For solving that, we will have to do a small hack by adding the ForAllMembers() method in order to ignore all the properties of the Record, since we already mapped them via constructor parameter. After the change, our mapping will look like this:

1
2
3
4
    CreateMap<PersonModel, PersonEntity>()
        .ForCtorParam(nameof(PersonEntity.FirstName), options => options.MapFrom(source => source.FirstName))
        .ForCtorParam(nameof(PersonEntity.LastName), options => options.MapFrom(source => source.LastName))
        .ForAllMembers(options => options.Ignore());

To be sure that this is going to work, we can write a unit-test that maps a Record from one type to another:

1
2
3
4
5
6
7
8
9
10
    [Fact]
    public void Mapping_CheckRecordsMapping()
    {
        PersonModel personModel = new("Christos", "Monogios");

        PersonEntity personEntity = mapper.Map<PersonModel, PersonEntity>(personModel);

        Assert.Equal(personModel.FirstName, personEntity.FirstName);
        Assert.Equal(personModel.LastName, personEntity.LastName);
    }

I hope this example clarifies your questions, feel free to leave a comment for any other ideas.

comments powered by Disqus