Years ago, I joined a group of consultants that were building a cloud-based health insurance quoting application. Due to the increasing popularity of the new platform among the network of insurance agents, it was obvious there were areas of performance that were lacking. Rather than relying on best guesses, we spent a sprint to focus on improvement.

This is obvious, but before you improve a platform, you need to analyze it. This means leveraging load test tools, capturing performance statistics like CPU and memory usage, and even modifying the code with stopwatch operations.

One of the biggest problems we uncovered was a convenience library we had chosen to bind database results to our business classes, and vice versa – Automapper.

Issues with Automapper

Google ‘automapper issues’ and you’ll see a lot of articles on why developers choose not to use Automapper.

  • Runtime errors: If you forget to create the mapper between two classes, you won’t know until it throws an exception
  • Debugging: If you’re properly logging, an exception will get you in the vicinity, but not necessarily the line number or exact issue
  • Performance: Depending on the complexity of your mappings, performance can be an issue

In our case, we were used to the peculiarities of debugging and runtime errors, but performance was indeed a shock.

Re-testing performance

I joined another project recently that uses Automapper, and initially became concerned. This time, though, the mappings between classes were not nearly as complex. This seemed like a great time to see how Automapper performed with simple bindings, compared to explicit conversion.

I’ve added a Conversion Comparison project to the Real World Impact repository on GitHub.

Extension methods for conversion

Using an extension method, an explicit conversion would look like this:

    public static class PersonExtensions
    {
        public static Person ConvertToPerson(this PersonDto personDto)
        {
            return new Person()
            {
                Id = personDto.Id.ToString(),
                FirstName = personDto.FirstName,
                LastName = personDto.LastName,
                FullName = $"{personDto.FirstName} {personDto.LastName}"
            };
        }

        public static PersonDto ConvertToPersonDto(this Person person)
        {
            return new PersonDto()
            {
                Id = Guid.Parse(person.Id),
                FirstName = person.FirstName,
                LastName = person.LastName
            };
        }
    }

It would be used like this:

Person person = dto.ConvertToPerson();

Automapper conversion

With Automapper, you create maps between classes, where properties are handled implicitly, unless you modify the configuration expression:

    public static class PersonMapper
    {
        public static void Build(IMapperConfigurationExpression ce)
        {
            ce.CreateMap<Person, PersonDto>();
            ce.CreateMap<PersonDto, Person>();
        }
    }

You then “build” the mapper configuration:

    public static class MapperBuilder
    {
        public static IMapper Build()
        {
            var config = new MapperConfiguration(config =>
            {
                PersonMapper.Build(config);
            });
            return config.CreateMapper();
        }
    }

This can be built when the application launches:

services.AddSingleton<IMapper>(mapper => MapperBuilder.Build());

And is used like this:

Person person = _mapper.Map<Person>(dto);

Test approach

Outside of the very first Automapper conversion, there isn’t really a difference in the amount of time it takes to convert a single object. So the true test comes as we convert lists of objects. If we are processing a list of objects, we will probably choose one of these approaches:

  • “for” loop
  • “for each” loop
  • LINQ

Since we are testing Automapper vs explicit extension method conversions, that gives us six test methods.

Automapper – “for” loop:

for (int i = 0; i < _personDtos.Count; i++)
{
    converted.Add(_mapper.Map<Person>(_personDtos[i]));
}

Automapper – “for each” loop:

foreach (var dto in _personDtos)
{
    converted.Add(_mapper.Map<Person>(dto));
}

Automapper – LINQ:

var converted = _personDtos.Select(dto => _mapper.Map<Person>(dto)).ToList();

Extension method – “for” loop:

for (int i = 0; i < _personDtos.Count; i++)
{
    converted.Add(_personDtos[i].ConvertToPerson());
}

Extension method – “for each” loop:

foreach (var dto in _personDtos)
{
    converted.Add(dto.ConvertToPerson());
}

Extension method – LINQ:

var converted = _personDtos.Select(dto => dto.ConvertToPerson()).ToList();

To test these methods, we’ll create object lists of a specified quantity, and execute the conversions a specified number of iterations. Furthermore, we can run each conversion method sequentially or in parallel.

Results

  • “QTY” – object list quantity
  • “LOOP” – iterations
  • “SEQ / PAR” – sequential or parallel execution
  • “TOTAL” – total average test execution time (ms)
  • “AM / FOR” – Automapper “for” average time (ms)
  • “AM / FE” – Automapper “for each” average time (ms)
  • “AM / LINQ” – Automapper LINQ average time (ms)
  • “EXT / FOR” – Extension method “for” average time (ms)
  • “EXT / FE” – Extension method “for each” average time (ms)
  • “EXT / LINQ” – Extension method LINQ average time (ms)

In all cases, for larger object lists, Automapper with LINQ performs exceedingly well.

What’s more, even if you take the “hit” by running large operations in parallel, you can get overall better performance. This assumes, of course, that the machine has multiple available cores.

Conclusion

Say what you will about Automapper, performance shouldn’t be your primary complaint. Regarding the other issues, as long as you remember to create your mapping classes, add them to the “build” logic, and keep the mappings straight-forward, you’ll be alright.

Leave a comment

Your email address will not be published. Required fields are marked *