II — You probably won’t need AutoMapper but…

Tiago Martins
3 min readJan 2, 2023

--

On the first part of “You probably won’t need AutoMapper but…” it was mentioned the benefits of using AutoMappers ProjectTo.

The .ProjectTo<T>() will tell AutoMapper’s mapping engine to emit a select clause to the IQueryable that will inform entity framework that it only needs to query the Name column of a given table, same as if you manually projected your IQueryable to an T with a Select clause.

Are there other options that doesn’t involve using third-party libraries? The answer is yes! While reusing the same example as the previous article we will check the following conversion methods:

  • Using AutoMapper;
  • Using extension methods;
  • Using implicit operators;

Example

A method should return a list of humans that have at least one cat as a pet.

public async Task<List<Human>> GetHumansWithCatsAsync()
{
return await _repositoryContext
.Humans
.Include(_ => _.Pets)
.Where(human => human.Pets.Any(pet => pet.PetType == PetType.Cat))
.Take(50)
.ToListAsync();
}

Using AutoMapper

When using CreateMap, AutoMapper uses optimizers to generate the code that enables the actual mapping . This is accomplished by using a mixture of expression tree compilation and Reflection.Emit.

Reflection.Emit belongs System.Reflection.Emit namespace, which allows the compiler or tool to be able to create new IL instructions. IL instructions are what the compiler outputs when the application gets compiled.

// Code
public class HumanProfile : Profile
{
public HumanProfile()
{
CreateMap<Human, HumanResponse>();
}
}

public class PetProfile : Profile
{
public PetProfile()
{
CreateMap<Pet, PetResponse>();
}
}

//Example
_ = _mapper.Map<HumanResponse>(human);

Using implicit operators

An implicit operator allows the conversion of one type to another without any data loss.

// Code
public static implicit operator HumanResponse(Human entry)
{
return new HumanResponse
{
Id = entry.Id,
Name = entry.Name,
Pets = entry.Pets.Select(pet => (PetResponse)pet).ToList()
};
}

public static implicit operator PetResponse(Pet entry)
{
return new PetResponse
{
Id = entry.Id,
PetType = entry.PetType,
};
}

//Example
HumanResponse _ = human;

Using extension methods

An extension method enables the addition of new methods into existing types without modifying the original type.

//Code
public static PetResponse ToPetResponse(this Pet entry)
{
return new PetResponse
{
Id = entry.Id,
PetType = entry.PetType,
};
}

public static HumanResponse ToHumanResponse(this Human entry)
{
return new HumanResponse
{
Id = entry.Id,
Name = entry.Name,
Pets = entry.Pets.Select(pet => pet.ToPetResponse()).ToList()
};
}

//Example
_ = human.ToHumanResponse();

When not to use AutoMapper

AutoMapper is pretty useful in mappings that are very clear on the end result — meaning that it should be pretty clear what the conversion is. Every project has its needs and quirks, but I would advise not to use AutoMapper when:

  • There is business logic involved in the mapping;
  • There are more properties being ignored than the ones being mapped;
  • You want to catch mapping errors at compile-time;
  • When you are taking more time writing the mapping configurations rather than the time that you would take by writing the mapping by ourself (either with an operator or with an extension method);

When not to use the implicit operator

  • I would argue that this shouldn’t be used in Domain models as they shouldn’t really be responsible for any type of conversion;
  • As with AutoMapper if the mapping isn’t clear implicit conversions shouldn’t be used at all as it hides the meaning of it;

When not to use extension methods

  • The rule here is simple — if the extension method isn’t clear on its purpose, you probably won’t need it.
    Example:
    T.ToHumanResponse() would be clear that we are mapping one type to another;
    T.ParseAndCountPetTypes() has no clear objective and probably could be inside of some helper class or service.

Benchmarks

The benchmark result shows that implicit/extension methods are faster (although with a slightly more memory allocated) which should not be a problem at all for small to medium projects — but it would probably be something to think about on larger projects.

As the saying goes “ Premature optimization is the root of all evil” — so act accordingly;

You can see the actual project on Github (Set up the SQL Server environment with Docker).

Happy coding!

--

--