A Simple and Fast Object Mapper

Dotnet Boxed

I have a confession to make...I don't use Automapper. For those who don't know Automapper is the number one object to object mapper library on NuGet by far. It takes properties from one object and copies them to another. I couldn't name the second place contender and looking on NuGet, nothing else comes close. This post talks about object mappers, why you might not want to use Automapper and introduces a faster, simpler object mapper that you might want to use instead.

Why use an Object Mapper

This is a really good question. Most of the time, it boils down to using Entity Framework. Developers want to be good citizens and not expose their EF Core models in the API surface area because this can have really bad security implications (See overposting here).

I have received a lot of comments at this point in the conversation saying "Why don't you use Dapper instead. Then you don't need model classes for your data layer, you can just go direct to your view model classes via Dapper". Dapper is really great, don't get me wrong but it's not always the right tool for the job, there are distinct disadvantages to using Dapper instead of EF Core:

  1. I have to write SQL. That's not so bad (You should learn SQL!) but it takes time to context switch and you often find yourself copying and pasting code back and forth from SQL Management Studio or Azure Data Studio (I've started using it, you should too). It just makes development a bit slower, that's all.
  2. EF Core can be run in-memory, making for very fast unit tests. With Dapper, I have to run functional tests against a real SQL Server database which is slow, brittle and a pain to setup. Before each test, you need to ensure the database is setup with just the right data, so your tests are repeatable, otherwise you end up with flaky tests. Don't underestimate the power of this point.
  3. EF Core Migrations can automatically generate the database for me. With Dapper, I have to use external tools like Visual Studio Database Projects, DbUp or Flyway to create my database. That's an extra headache at deployment time. EF Core lets you cut out the extra time required to manage all of that.
  4. EF Core Migrations can automatically handle database migrations for me. Migrating databases is hard! Keeping track of what state the database is in and making sure you've written the right ALTER TABLE scripts is extra work that can be automated. EF Core handles all that for me. Alternatively, Visual Studio Database Projects can also get around this problem.
  5. I can switch database provider easily. Ok...ok...nobody does this in the real world and I can only think of one case where this happened. People always mention this point though for some reason.
  6. EF Core defaults to using the right data types, while on the other hand human beings...have too often chosen the wrong data types and then paid the penalties later on when the app is in production. Use NVARCHAR instead of VARCHAR and DATETIMEOFFSET instead of DATETIME2 or even DATETIME people! I've seen professional database developers make these mistakes all the time. Automating this ensures that the correct decision is made all the time.
  7. EF Core is not that much slower than using Dapper. We're not talking about orders of magnitude slower as it was with EF6. Throwing away all of the above benefits for slightly better speed is not a trade-off that everyone can make though, it depends on the app and situation.

You need to use the right tool for the right job. I personally use Dapper, where there is an existing database with all the migrations etc. already handled by external tools and use EF Core where I'm working with a brand new database.

What is good about Automapper?

Automapper is great when you have a small project that you want to throw together quickly and the objects you are mapping to and from have the same or similar property names and structure.

It's also great for unit testing because once you've written your mapper, testing it is just a matter of adding a one liner to test that all the properties in your object have a mapping setup for them.

Finally if you use Automapper with Entity Framework, you can use the ProjectTo method which uses the property mapping information to limit the number of fields pulled back from your database making the query a lot more efficient. I think this is probably the biggest selling point of Automapper. The alternative is to write your own Entity Framework Core projection.

What is wrong with Automapper?

Cezary Piatek writes a very good rundown of some of the problems when using Automapper. I'm not going to repeat what he says but here is a short description:

  1. In the real world, mapping between identical or similar classes is not that common.
  2. If you have similar classes you are mapping between, there is no guarantee that they will not diverge, requiring you to write increasingly complex Automapper code or rewriting the mapping logic without Automapper.
  3. Finding all usages of a property no longer works when using Automapper unless you explicitly map every property, lowering discoverability.
  4. If you have a complex scenario, Jimmy Bogard (the author of the tool) suggests not using Automapper:

    • DO NOT use AutoMapper except in cases where the destination type is a flattened subset of properties of the source type.
    • DO NOT use AutoMapper to support a complex layered architecture.
    • AVOID using AutoMapper when you have a significant percentage of custom configuration in the form of Ignore or MapFrom.
  5. If you're mapping from database models to view models in an API, then dumping your database schema out as JSON makes for a bad API. You usually want more complex nested objects.
  6. How much time does it really save? Object mapping code is the simplest code a developer can write, I can do it without thinking and knock a few mappings out in a couple of minutes.
  7. Automapper is complex, it has a massive documentation site just to show you how to use it and just checkout the 29 point list of guidelines on how to use it. Why should copying values from one object to another need to be so complex?

A Simple and Fast Object Mapper

I wrote an object mapper library that consists of a couple of interfaces and a handful of extension methods to make mapping objects slightly easier. The API is super simple and very light and thus fast. You can use the Boxed.Mapping NuGet package or look at the code at on GitHub in the Dotnet-Boxed/Framework project. Lets look at an example. I want to map to and from instances of these two classes:

public class MapFrom
{
    public bool BooleanFrom { get; set; }
    public DateTimeOffset DateTimeOffsetFrom { get; set; }
    public int IntegerFrom { get; set; }
    public string StringFrom { get; set; }
}

public class MapTo
{
    public bool BooleanTo { get; set; }
    public DateTimeOffset DateTimeOffsetTo { get; set; }
    public int IntegerTo { get; set; }
    public string StringTo { get; set; }
}

The implementation for an object mapper using the .NET Boxed Mapper is shown below. Note the IMapper interface which is the heart of the .NET Boxed Mapper. There is also an IAsyncMapper if for any reason you need to map between two objects asynchronously, the only difference being that it returns a Task.

public class DemoMapper : IMapper<MapFrom, MapTo>
{
    public void Map(MapFrom source, MapTo destination)
    {
        destination.BooleanTo = source.BooleanFrom;
        destination.DateTimeOffsetTo = source.DateTimeOffsetFrom;
        destination.IntegerTo = source.IntegerFrom;
        destination.StringTo = source.StringFrom;
    }
}

And here is an example of how you would actually map a single object, array or list:

public class UsageExample
{
    private readonly IMapper mapper = new DemoMapper();
    
    public MapTo MapOneObject(MapFrom source) => this.mapper.Map(source);
    
    public MapTo[] MapArray(List source) => this.mapper.MapArray(source);
    
    public List MapList(List source) => this.mapper.MapList(source);
}

I told you it was simple! Just a few convenience extension methods bundled together with an interface that makes it just ever so slightly quicker to write object mapping than rolling your own implementation. If you have more complex mappings, you can compose your mappers in the same way that your models are composed.

Performance

Keeping things simple makes the .NET Boxed Mapper fast. I put together some benchmarks using Benchmark.NET which you can find here. The baseline is hand written mapping code and I compare that to Automapper and the .NET Boxed Mapper.

I even got a bit of help from the great Jon Skeet himself on how to improve the performance of instantiating an instance when using the generic new() constraint which it turns out is pretty slow because it uses Activator.CreateInstance under the hood.

Object to Object Mapping Benchmark

This benchmark measures the time taken to map from a MapFrom object to the MapTo object which I show above.

Simple object to object mapping benchmark

MethodRuntimeMeanRatioGen 0/1k OpAllocated Memory/Op
BaselineClr7.877 ns1.000.017856 B
BoxedMapperClr25.431 ns3.070.017856 B
AutomapperClr264.934 ns31.970.027788 B
BaselineCore9.327 ns1.000.017856 B
BoxedMapperCore17.174 ns1.840.017856 B
AutomapperCore158.218 ns16.970.027988 B

List Mapping Benchmark

This benchmark measures the time taken to map a List of MapFrom objects to a list of MapTo objects.

List to list mapping benchmark

MethodRuntimeMeanRatioGen 0/1k OpAllocated Memory/Op
BaselineClr1.833 us1.002.05426.31 KB
BoxedMapperClr3.295 us1.802.05236.31 KB
AutomapperClr10.569 us5.772.48727.65 KB
BaselineCore1.735 us1.002.05426.31 KB
BoxedMapperCore2.237 us1.292.05236.31 KB
AutomapperCore3.220 us1.862.48727.65 KB

Speed

It turns out that Automapper does a really good job on .NET Core in terms of speed but is quite a bit slower on .NET Framework. This is probably down to the intrinsic improvements in .NET Core itself. .NET Boxed is quite a bit faster than Automapper on .NET Framework but the difference on .NET Core is much less at around one and a half times. The .NET Boxed Mapper is also very close to the baseline but is a bit slower. I believe that this is due to the use of method calls on interfaces, whereas the baseline mapping code is only using method calls on concrete classes.

Zero Allocations

.NET Boxed has zero allocations of memory while Automapper allocates a small amount per mapping. Since object mapping is a fairly common operation these small differences can add up over time and cause pauses in the app while the garbage collector cleans up the memory. There seems to be a trend I've seen in .NET for having zero allocation code. If you care about that, then this might help.

Conclusions

What I've tried to do with the .NET Boxed Mapper is fill a niche which I thought that Automapper was not quite filling. A super simple and fast object mapper that's just a couple of interfaces and extension methods to help you along the way and provide a skeleton on which to hang your code. If Automapper fits your app better, go ahead and use that. If you think it's useful, you can use the Boxed.Mapping NuGet package or look at the code at on GitHub in the Dotnet-Boxed/Framework project.

Web Mentions

What's this?

0 Replies

GitHub Comments

RehanSaeed

Brent Brent commented on 2019-03-06 17:45:04

What an incredibly well done post! Than you! Ok, I have a question. Normally I presume that during testing, my IOC container or configuration will repoint my SQL Server connection to a more light-weight tool such as SQL CE or LocalDB.
You implied that providing sample data, for testing purposes, to SQL CE or LocalDB is difficult in comparison with using EF Core in-memory. Why is that so?

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2019-03-06 18:42:33

What an incredibly well done post! Than you! Ok, I have a question. Normally I presume that during testing, my IOC container or configuration will repoint my SQL Server connection to a more light-weight tool such as SQL CE or LocalDB.
You implied that providing sample data, for testing purposes, to SQL CE or LocalDB is difficult in comparison with using EF Core in-memory. Why is that so?

Perhaps I could have been clearer. There are a few extra problems when you have to deal with a real database, as opposed to an in-memory database:

  1. You have to have a real SQL Server provisioned. This may be simple if we're talking about a developer machine but what about when we run the tests in a CI server? Do you pay for a server just for functional tests?
  2. You need to create the database, database schema and the user needed to connect to the database. You also might need to think about migrating the database (See next item).
  3. You could drop the database and recreate it after each test but that would be dreadfully slow. Instead you should probably look at a tool like Respawn (Another great project by Jimmy Bogard) which clears the database instead making the test a lot quicker. The downside is that now you have to deal with migrations as I mention above.

Even after all this there are downsides. It will still be slower than an in-memory database, a lot more complicated and possibly more costly. On the upside, you have something that is a lot more real-world, so you may catch bugs that you wouldn't catch if you used an in-memory database.

RehanSaeed

James James commented on 2019-03-12 14:48:03

This is great. I've been doing something very similar. What I would love to have is a VS extension or tool to autogenerate these mappings.

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2019-03-12 16:02:50

This is great. I've been doing something very similar. What I would love to have is a VS extension or tool to autogenerate these mappings.

That would be a nice extension!

RehanSaeed

Jason Jason commented on 2019-03-28 12:57:40

Great article and great work achieving these performance gains! I'm curious, what motivated you to create a new mapper when AutoMapper is essentially the de facto standard in this area? Did you run into performance issues with AutoMapper or were you generally just looking for something more light weight?

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2019-03-28 13:06:33

Great article and great work achieving these performance gains! I'm curious, what motivated you to create a new mapper when AutoMapper is essentially the de facto standard in this area? Did you run into performance issues with AutoMapper or were you generally just looking for something more light weight?

I used to use Automapper and always found little annoyances with it. I believe the latest version is much improved though. In the end, I just thought, this is way over engineered, I just want to map some properties and I can do that by hand in 30 seconds. It'll be boring but it'll work, be dead simple and fast. I then started to write an interface and a few extension methods to help me write mapping code quicker which resulted in this blog post.

RehanSaeed
(edited )

ali ali commented on 2019-04-06 06:44:49

hi rehan
how to resolve this problem when using boxed mapper and DI in my project?
example :

public interface IPrepareProduct<TProduct, TProductDto>: IMapper<TProduct, TProductDto> 
{
}

public class PrepareProduct : IPrepareProduct<Product, ProductDto>
{
    public void Map(Product source, ProductDto destination)
    {
        destination.CreatedOn = source.CreatedOn;
        destination.Description = source.Description;
        destination.Name= source.Name;
        destination.FromDate= source.FromDate;
        destination.Id= source.Id;
        destination.InStockCount= source.InStockCount;
        destination.OldPrice= source.OldPrice;
        destination.Price= source.Price;
        destination.PreViewText= source.PreViewText;
        destination.Published = source.Published;
        destination.SKU= source.SKU;
        destination.ToDate= source.ToDate;
    }
}
/*******************************/
public interface IPreparePicture:IMapper<Picture,PictureDto>
{
}

public class PreparePicture : IMapper<Picture,PictureDto>
{
    public void Map(Picture source, PictureDto destination)
    {
        destination.Alt = source.Alt;
        destination.EntityId = source.EntityId;
        destination.EntityName = source.EntityName;
        destination.id = source.id;
        destination.IsDefault = source.IsDefault;
        destination.Order = source.Order;
        destination.Title = source.Title;
        destination.Url = source.Url;
    }
}
RehanSaeed

ali ali commented on 2019-04-06 11:51:42

how to ignore some property in your object mapper ?

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2019-04-08 16:15:27

how to ignore some property in your object mapper ?

Just don't write the line of code that does the mapping i.e.

destination.SomeProperty = source.SomeOtherProperty;
RehanSaeed
(edited )

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2019-04-08 16:54:53

hi rehan
how to resolve this problem when using boxed mapper and DI in my project?
example :

public interface IPrepareProduct<TProduct, TProductDto>: IMapper<TProduct, TProductDto> 
{
}

public class PrepareProduct : IPrepareProduct<Product, ProductDto>
{
    public void Map(Product source, ProductDto destination)
    {
        destination.CreatedOn = source.CreatedOn;
        destination.Description = source.Description;
        destination.Name= source.Name;
        destination.FromDate= source.FromDate;
        destination.Id= source.Id;
        destination.InStockCount= source.InStockCount;
        destination.OldPrice= source.OldPrice;
        destination.Price= source.Price;
        destination.PreViewText= source.PreViewText;
        destination.Published = source.Published;
        destination.SKU= source.SKU;
        destination.ToDate= source.ToDate;
    }
}
/*******************************/
public interface IPreparePicture:IMapper<Picture,PictureDto>
{
}

public class PreparePicture : IMapper<Picture,PictureDto>
{
    public void Map(Picture source, PictureDto destination)
    {
        destination.Alt = source.Alt;
        destination.EntityId = source.EntityId;
        destination.EntityName = source.EntityName;
        destination.id = source.id;
        destination.IsDefault = source.IsDefault;
        destination.Order = source.Order;
        destination.Title = source.Title;
        destination.Url = source.Url;
    }
}

You have not outlined the problem. Also, please raise a GitHub issue to get better answers.

RehanSaeed

David David commented on 2019-08-07 14:32:46

Hi Rehan,

fan of your work, as always.. In this case, I am looking for any advantages using this approach, over say, DTO Writer, which will auto-generate mappings:

https://marketplace.visualstudio.com/items?itemName=VitaliiIlchenko.DtoCreator

This creates static methods for ToModel and FromModel.

I admit to being a big fan of code generation.

Perhaps I am missing something, as I have only spent an hour or so looking at this?

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2019-08-07 15:02:18

Hi Rehan,

fan of your work, as always.. In this case, I am looking for any advantages using this approach, over say, DTO Writer, which will auto-generate mappings:

https://marketplace.visualstudio.com/items?itemName=VitaliiIlchenko.DtoCreator

This creates static methods for ToModel and FromModel.

I admit to being a big fan of code generation.

Perhaps I am missing something, as I have only spent an hour or so looking at this?

Hi David,

That's a really good question. This tool looks useful, I've not heard of it before but I've seen another one in the past. I've even thought about building my own once or twice. Questions I would have are:

  1. Does it help when you want to edit a DTO to generate code for new properties you've added, removed, renamed or where you have changed the type?
  2. Does it handle more complex objects that are not just POCO's? Things like immutable types where the only way to create the object is from the constructor.
  3. Does it handle nested POCO's? This is pretty common.

Overall, it might get you started quickly but I'm not sure that it will be flexible enough. Also, one thing that I've never understood is the time saving factor. It really doesn't take long to write a mapper, unless maybe you have lots to do.

RehanSaeed

David David commented on 2019-08-07 16:31:05

Hi David,

That's a really good question. This tool looks useful, I've not heard of it before but I've seen another one in the past. I've even thought about building my own once or twice. Questions I would have are:

  1. Does it help when you want to edit a DTO to generate code for new properties you've added, removed, renamed or where you have changed the type?
  2. Does it handle more complex objects that are not just POCO's? Things like immutable types where the only way to create the object is from the constructor.
  3. Does it handle nested POCO's? This is pretty common.

Overall, it might get you started quickly but I'm not sure that it will be flexible enough. Also, one thing that I've never understood is the time saving factor. It really doesn't take long to write a mapper, unless maybe you have lots to do.

Rehan, actually a combination of DTO Writer and this would seem to match most needs:

https://github.com/cezarypiatek/MappingGenerator

RehanSaeed

David David commented on 2019-08-07 16:50:23

Rehan, actually a combination of DTO Writer and this would seem to match most needs:

https://github.com/cezarypiatek/MappingGenerator

In my case, I very rarely only have a small number of classes to map, so generation saves a bit of time, and potentially errors when repetitively coding simple tasks.

For adding properties or something more complex than one-to-one mapping, I would probably just do that by hand. These tend to be the exceptions, rather than the rule, and it is not worth attempting to automate complex logic...

... unless someone else codes it for me ;-)

David

RehanSaeed

Ashish Ashish commented on 2019-09-03 10:04:54

How to manage different property name of entity ?

Lets say I have an entity

public class person
{
    public string name {get;set;}
}

public class personDTO
{
    public string personName {get;set;}
}
RehanSaeed

Christopher Christopher commented on 2019-09-20 12:20:43

A more extensive example mapping a simple object hierarchy would be really helpful.

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2019-09-23 09:00:46

How to manage different property name of entity ?

Lets say I have an entity

public class person
{
    public string name {get;set;}
}

public class personDTO
{
    public string personName {get;set;}
}

Easy:

destination.Name = source.PersonName;
RehanSaeed

Peter Morris Peter Morris commented on 2020-01-01 16:53:34

The problem with manually writing mappers is that you also have to manually write tests for them.

That takes a lot of time. AutoMapper does it for us.

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2020-01-01 17:03:30

The problem with manually writing mappers is that you also have to manually write tests for them.

That takes a lot of time. AutoMapper does it for us.

I don't think that code takes very long to write. You still have to write a test if you use Automapper. Docs suggest this:

var config = AutoMapperConfiguration.Configure();
config.AssertConfigurationIsValid();

It checks that every single member has a destination type. It may not be the right one (since there are always exceptional cases), but it at least tests that every property is copied from source to destination.

There are pros and cons. Automapper still has it's uses.

RehanSaeed

Sam Sam commented on 2020-01-04 13:11:26

I like the direction you went with this. We struggle back and forth with the same person points of automappers as you do. Mostly property references. I want to know what you consider different about your approach than manually mapping (I think you called this baseline)? Yes you are creating a mapping framework with the interface but you are still manually writing the mapping. I am not opposed to manually writing the mapping a because we do that now and it's the most performant, but isn't yours also manually mapping? How is your benchmark time slower?

RehanSaeed

Muhammad Rehan Saeed Muhammad Rehan Saeed commented on 2020-01-04 14:04:54

I like the direction you went with this. We struggle back and forth with the same person points of automappers as you do. Mostly property references. I want to know what you consider different about your approach than manually mapping (I think you called this baseline)? Yes you are creating a mapping framework with the interface but you are still manually writing the mapping. I am not opposed to manually writing the mapping a because we do that now and it's the most performant, but isn't yours also manually mapping? How is your benchmark time slower?

It's really just a set of extension methods to make mapping easier and interfaces to standardise things so developers know what to expect. That's it. Just enough to make manually mapping more bearable and slightly faster to write.

The time is mostly a bit slower because we use an interface and we also use a different method of newing up instances of types as opposed to the new keyword.