Back to Home

FlashMapper - an alternative to auto-mapper

c # · .net · automapper · flashmapper

FlashMapper - an alternative to auto-mapper

I don’t even know what an auto mapper is. Why do I need its alternative?


Imagine a situation, you have two classes for the same entity, one describes a data model from a user form, the second describes a database model. The properties of these classes coincide by 95 percent, the differences can only be in some timestamps or other system fields in the database model. When the user fills out the form, you get the model from this form, and then you need to convert it to the database model in order to save.

FlashMapper , like AutoMapper, is a .net library that saves you from writing routine code during the conversion process. It automatically matches all the same class properties, leaving you only the need to resolve differences.

In addition to getting rid of routine work, this approach provides an additional check of the correctness of the code of your program. Imagine that you added some new properties to these classes, and forgot to link them. In the best case, you will find out about this somewhere at the testing stage, in the worst a month after the release, when you notice that your database does not have new data that users carefully enter into a new field on the form. FlashMapper will either bind new properties automatically if it can, or throw an exception the next time the application starts.

If you already have an auto-mapper, then why make another such library?


In the auto-mapper, I never liked its monstrous syntax. Just to connect two properties in different classes you need to register three lambdas in the configuration in order to ignore the property - two. Also, at the last place of work, I often began to have a need to 'map' data from two classes into one. I did not find a simple solution to this problem using the standard auto-mapper tools, so I started writing an extension library for it that allows us to create mappings from two or more source classes into one, and even almost finished it. But then I suddenly came up with the idea of ​​a simpler syntax, and I decided to drop everything and write my library from scratch.

Actually, FlashMapper has a simpler and more user-friendly syntax, provides the ability to map from several objects to one, and also provides an API for putting each individual separate mapping configuration into its own class, into which you can forward some logic as a dependency through constructor. Also, according to the idea, he should have had better performance, but for some reasons, which are lower, the performance turned out to be at the level of the auto-camper.

Syntax


Imagine that we have two classes, one defines the data model that comes to the server from the user form, the second defines the storage model in the database.

Custom form model
public class UserForm
{
    public Guid? Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Town { get; set; }
    public string State { get; set; }
    public DateTime BirthDate { get; set; }
    public string Login { get; set; }
    public string Password { get; set; }
}


Database model
public class UserDb
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Town { get; set; }
    public string State { get; set; }
    public DateTime BirthDate { get; set; }
    public string Login { get; set; }
    public string PasswordHash { get; set; }
    public DateTime RegistrationTime { get; set; }
    public byte[] Timestamp { get; set; }
    public bool IsDeleted { get; set; }
}


Then here is how the mapping configuration from the first class to the second will look like:

public class FlashMapperInitializer : IInitializer // Не является частью FlashMapper'а
{
    private readonly IMappingConfiguration mappingConfiguration; // Синглтон объект, который хранит в себе все конфигурации
    private readonly IPasswordHashCalculator passwordHashCalculator;
    public FlashMapperInitializer(IMappingConfiguration mappingConfiguration, IPasswordHashCalculator passwordHashCalculator)
    {
        this.mappingConfiguration = mappingConfiguration;
    }
    public void Init() // Код, который запускается во время инициализации приложения
    {
        mappingConfiguration.CreateMapping(u => new UserDb
        {
            Id = u.Id ?? Guid.NewGuid(),
            Login = u.Id.HasValue ? MappingOptions.Ignore() : u.Login,
            RegistrationTime = u.Id.HasValue ? MappingOptions.Ignore() : DateTime.Now,
            Timestamp = MappingOptions.Ignore(),
            IsDeleted = false,
            PasswordHash = passwordHashCalculator.Calculate(u.Password)
        });
        //mappingConfiguration.CreateMapping <...> (...); // Прочие конфигурации
    }
}

The syntax is reminiscent of creating a new object and initializing its properties based on the source object. In fact, the parameter of the CreateMapping method is a lambda expression, which is then complemented by matching the remaining properties of the UserDb class with similar properties from the UserForm class. Also, based on this expression, another one is created that does not create a new object of type UserDb, but simply copies the data into an existing object. Both expressions are compiled and stored in mappingConfiguration for later use.

Here's how you can then use the created mapping:

public class UserController : Controller
{
    private readonly IMappingConfiguration mappingConfiguration;
    private readonly IRepository usersRepository;
    public UserController(IMappingConfiguration mappingConfiguration, IRepository usersRepository)
    {
        this.mappingConfiguration = mappingConfiguration;
        this.usersRepository = usersRepository;
    }
    [HttpPost]
    public ActionResult Edit(UserForm model)
    {
        if (!ModelState.IsValid)
            return View(model);
        var existingUser = usersRepository.Find(model.Id);
        if (existingUser == null)
        {
            var newUser = mappingConfiguration.Convert(model).To();
            usersRepository.Add(newUser);
        }
        else
        {
            mappingConfiguration.MapData(model, existingUser);
            usersRepository.Update(existingUser);
        }
        return View(model);
    }
}

Multiple Source Mapping


FlashMapper allows you to create mappings with multiple sources (up to 15):

mappingConfiguration.CreateMapping((s1, s2, s3) => new Destination { ... });

In this case, when automatically matching properties, suitable ones will be searched in each source. If at the same time a suitable property is found in several sources, FlashMapper will throw an exception that a collision has occurred. To prevent this from happening, you must either manually specify from which source to take the desired property, or specify in the mapping settings CollisionBehavior = ChooseAny.

mappingConfiguration.CreateMapping((s1, s2, s3) => new Destination { ... }, o => o.CollisionBehavior(SelectSourceCollisionBehavior.ChooseAny));

Despite the fact that this behavior is called ChooseAny, a suitable property will not be chosen randomly, the priority depends on the serial number of the source. The first will have maximum priority, then the second, the third and so on.

API for putting each mapping into a separate service


FlashMapper provides a set of interfaces and base classes for putting the configuration of each mapping into a separate class. This approach allows you to better structure the code, all configurations do not lie in the same method with initialization, but each in its own service. Dependencies can be thrown into the service through the constructor, and thanks to this, use complex business logic inside the mapping.

Here's how the mapping configuration from the example above will change:

public interface IUserDbBuilder : IBuilder { }
public class UserDbBuilder : FlashMapperBuilder, IUserDbBuilder
{
    private readonly IPasswordHashCalculator passwordHashCalculator;
    public UserDbBuilder(IMappingConfiguration mappingConfiguration, IPasswordHashCalculator passwordHashCalculator) : base(mappingConfiguration)
    {
        this.passwordHashCalculator = passwordHashCalculator;
    }
    protected override void ConfigureMapping(IFlashMapperBuilderConfigurator configurator)
    {
        configurator.CreateMapping(u => new UserDb
        {
            Id = u.Id ?? Guid.NewGuid(),
            Login = u.Id.HasValue ? MappingOptions.Ignore() : u.Login,
            RegistrationTime = u.Id.HasValue ? MappingOptions.Ignore() : DateTime.Now,
            Timestamp = MappingOptions.Ignore(),
            IsDeleted = false,
            PasswordHash = passwordHashCalculator.Calculate(u.Password)
        });
    }
}

The IUserDbBuilder interface has two methods - UserDb Build (UserForm source) and void MapData (UserForm source, UserDb destination). Base class FlashMapperBuilder implements them, but leaves for implementation the void ConfigureMapping (IFlashMapperBuilderConfigurator methodconfigurator). This method is called during initialization and creates mapping in the passed mappingConfiguration. Note that the last generic parameter of the abstract class FlashMapperBuilder must be its implementation, in this case UserDbBuilder.

Consider the configuration process in more detail. The FlashMapperBuilder <> base class also implements the IFlashMapperBuilder interface, which in turn provides the RegisterMapping method, which must be called during application initialization. As a result, the implementation of UserDbBuilder must be registered in the IoC container for two interfaces - IUserDbBuilder for further use, and IFlashMapperBuilder for registering mapping.

Something like that for Ninject
Kernel.Bind().To();


To simplify this syntax, I use a small extension method
public static IBindingWhenInNamedWithOrOnSyntax AsFlashMapperBuilder(
    this IBindingWhenInNamedWithOrOnSyntax builder) where T : IFlashMapperBuilder
{
    builder.Kernel.AddBinding(new Binding(typeof(IFlashMapperBuilder), builder.BindingConfiguration));
    return builder;
} 

As a result, the registration of the builder in the container can be rewritten as follows:
Kernel.Bind().To().AsFlashMapperBuilder();


The library has the IFlashMapperBuildersRegistrationService service, with the RegisterAllBuilders method, this method must be called during application initialization. The service simply takes all the implementations of the IFlashMapperBuilder interface that are passed to it in the constructor, and calls the RegisterMappings method on them.

Here is an example of using the IUserDbBuilder service
public class UserController : Controller
{
    private readonly IUserDbBuilder userDbBuilder;
    private readonly IRepository usersRepository;
    public UserController(IUserDbBuilder userDbBuilder, IRepository usersRepository)
    {
        this.userDbBuilder = userDbBuilder;
        this.usersRepository = usersRepository;
    }
    [HttpPost]
    public ActionResult Edit(UserForm model)
    {
        if (!ModelState.IsValid)
            return View(model);
        var existingUser = usersRepository.Find(model.Id);
        if (existingUser == null)
        {
            var newUser = userDbBuilder.Build(model);
            usersRepository.Add(newUser);
        }
        else
        {
            userDbBuilder.MapData(model, existingUser);
            usersRepository.Update(existingUser);
        }
        return View(model);
    }
}


In theory, there should be one problem. Let's look at the ConfigureMapping method from the configuration example. The lambda expression that describes mapping uses the class field, passwordHashCalculator. In general, lambda expressions, as well as methods resulting from their compilation, are always static. But when inside an expression you refer to some non-static member of the class (field, property, method), the current instance of the class that creates the expression is passed to it as a constant, and then the values ​​of the fields of this instance are used. It turns out that the instance of the builder that was used to register the mapping will fall into the mapping expression, and the values ​​of its fields will always be used.

To prevent this, the expression passed to the CreateMapping method is modified. Another parameter is added to it - the builder itself, and all calls to the current instance of the builder are replaced by a call to this parameter. As a result, mapping from two parameters is obtained, the first is the data itself, the second is the builder. When the builder later calls the Build method, it takes this mapping and passes the data and itself as parameters.

It is because of this that the last generic parameter of the FlashMapperBuilder class must be the builder itself. Of course, it would be possible to calculate this type during runtime, but I wanted to avoid any late binding when using ready-made compiled mappings.

This approach also allows the use of several sources, a maximum of 14.

Performance


I never really bothered about the performance of the auto-mapper, but from my colleagues I heard that it works 10 times slower than a similar manual mapping. I then decided that it is smarter than everyone, and I will somehow get the performance comparable to manual mapping. It would seem that when using ready-made mapping, I already have a compiled method, the IL-code of which should coincide with the IL-code of the method that does the same job, but written by hand, there is no late binding when using mappings, you just need to get a pointer to the method and call him. But in fact, the performance of my mapper turned out to be comparable with the performance of an auto mapper.

I was very surprised, started profiling the code, my code did not cause any brakes, the main time of execution was eating the compiled mapping method. I downloaded the dotnet symbols, and ran the profiler again. It turned out that 72% of the processor time is occupied by a call to a certain method JIT_MethodAccessCheck.

I started googling what this method does in general. In the search results there was a certain jithelpers.h file from the sorts of gihab, as well as questions on stackoverflow from people who were also concerned about the performance of compiled lambda expressions. With stackoverflow, I found out that this method is somehow related to CAS (code access security), and also found another way to compile lambda expressions. I decided to just blindly use this method to see what kind of performance it turns out. And, lo and behold, my compiled method from an expression began to work at the same speed as a similar manual method. But, unfortunately, after this two tests fell off.

Tests for dependency injection broke. Compiled in a new way expressions lost access to the private fields of the class in which they were created. Moreover, they were compiled without errors, the error popped up already at the time of conversion from one class to another. To be honest, I did not even suspect up to this point that every time I checked it, it checked each time that this piece of compiled code had access to one or another member of the class.

I began to figure out what's what. The method of compiling lambda expressions that was advised on stackoverflow uses the older and lower-level subnet API to generate IL code. These are the MethodBuilder, TypeBuilder, and other classes. It was necessary to create them with your hands, define parameters, pass an instance of MethodBuilder into the CompileToMethod method of the lambda expression. This method recorded the IL code directly of what was in the expression, so the resulting method worked quickly. After that, it was necessary to create a dynamic type from an instance of the TypeBuilder class, and pull the desired method out of it by reflection. It’s clear that since I create a new type and the method is already defined in it, the method loses access to the private fields of the class in which the lambda expression was defined. The standard way to compile a lambda expression is a simple call to the Compile method, this method appeared in later versions of subnet, and uses a newer API. This is the DynamicMethod class. It does not require hands to create dynamic types and so on, but simply provides methods for writing IL code into it and obtaining the result. Although inside it also creates a new type in the same way, it can somehow disable the check of the availability of class members at runtime, it even has a special flag for this in the constructor. And, apparently, he adds the JIT_ MethodAccessCheck call to the compiled method. it just provides methods for writing IL code into it and getting the result. Although inside it also creates a new type in the same way, it can somehow disable the check of the availability of class members at runtime, it even has a special flag for this in the constructor. And, apparently, he adds the JIT_ MethodAccessCheck call to the compiled method. it just provides methods for writing IL code into it and getting the result. Although inside it also creates a new type in the same way, it can somehow disable the check of the availability of class members at runtime, it even has a special flag for this in the constructor. And, apparently, he adds the JIT_ MethodAccessCheck call to the compiled method.

I rummaged around in the insides of the subnet, tried in various ways to get around the field availability check. For example, I tried to make the class created by TypeBuilder a nested class of the one I need to access the fields of, also tried to access the fields through reflection, but there were other problems. In general, so far I have not managed to get the stackoverflow method to work, and I rolled everything back to the standard compilation method.

In principle, the results are not so bad, FlashMapper and AutoMapper work 4 times slower than the manual method. A million conversions take 400 ms, compared to 100 ms for the manual method. Simple data transfer, when new instances of the resulting class are not created, works a little faster.

image

Although it’s a shame, of course, that the results do not correspond to the name of the library, so I dig a little more, maybe something will turn out.

Conclusion


At the moment, FlashMapper has some basic auto-mapper functionality, in addition, it has a more compact configuration syntax, allows mapping from several objects and has an API for transferring configurations to separate services.

The main assembly of the library

Assembly with extensions for mapping from several sources

Assembly with API for making configurations to services

Source codes on github

Documentation

Read Next