Skip to content
jeuxjeux20 edited this page Sep 4, 2021 · 3 revisions

Why NotSoAutoMapper

When building an API, you'll encounter DTOs — also named API models. When using Entity Framework, you'll have to project your database models into your DTOs. This should look like something similar to this:

var people = await _dbContext.Select(x => new PersonDto
{
    Id = x.Id,
    FirstName = x.FirstName,
    LastName = x.LastName
}).ToListAsync();

Then, a problem arises: how to reuse those expressions? The most obvious way to do that is to create a static readonly field with Expression<Person, PersonDto>. But, we then hit a roadblock: with EF's IQueryable<T>, expressions are not reusable inside other expressions. Basically, the following will throw:

var people = await _dbContext.Select(x => new PersonDto
{
    Id = x.Id,
    FirstName = x.FirstName,
    LastName = x.LastName,
    FavoriteBooks = x.FavoriteBooks.Select(BookDto.MappingExpression).ToList() // Doesn't work!!!
}).ToListAsync();

NotSoAutoMapper fixes this problem by wrapping expressions inside a Mapper<TSource, TResult> and transforming the expressions by inlining any usage of the mapper. Little bonus: you can also write custom methods that get transformed into another expression, very useful for reusing some code inside IQueryables!

Creating mappers

Make sure to install the NotSoAutoMapper package from NuGet!

Let's assume that we have the following classes:

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

    public string FirstName { get; set; }
    public string LastName { get; set; }

    public long SocialSecurityNumber { get; set; }
}

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

    public string FirstName { get; set; }
    public string LastName { get; set; }
}

To map a Person to a PersonDto, we can use Mapper<Person, PersonDto>. Let's add it to the PersonDto class.

public class PersonDto
{
    // ... <Previous properties>
    public static readonly Mapper<Person, PersonDto> Mapper = new(x => new PersonDto
    {
        Id = x.Id,
        FirstName = x.FirstName,
        LastName = x.LastName
    });
}

That's it! The code is pretty straight forward, we're declaring a Mapper which is simply mapping all the properties from Person to PersonDto. Writing this code manually is a bit tedious, fortunately the Visual Studio extension MappingGenerator is there to generate this boilerplate code for us.

Using mappers

There are many ways of using mappers. They are all usable inside Mapper expressions, and outside of them as well. In order to use mappers inside IQueryable<T> expressions, you need to use them inside a Mapper, because expressions need to be transformed to inline the mappers' expressions.

Mapping one element

Use element.MapWith(mapper) or mapper.Map(element).

Examples

new Mapper<Book, BookDto>(b => new BookDto
{
    Id = b.Id,
    Name = b.Name,
    Author = b.Author.MapWith(PersonDto.Mapper)
});
var personDto = person.MapWith(PersonDto.Mapper);

Mapping an IEnumerable<T>/IQueryable<T>

Use enumerable.MapWith(mapper). This can also safely be used with IQueryable<T>.

Examples

new Mapper<Team, TeamDto>(b => new TeamDto
{
    Id = b.Id,
    Name = b.Name,
    Members = b.Members.MapWith(PersonDto.Mapper)
});
var teamDtos = await _context.Teams.MapWith(TeamDto.Mapper).ToListAsync();