NextGenMapper by Anton Ryabchikov
Nuget / site data
Details
Info
Name: NextGenMapper
Package Description
Author: Anton Ryabchikov
NuGet: https://www.nuget.org/packages/NextGenMapper/
You can find more details at https://github.com/DedAnton/NextGenMapper
Original Readme
Extremely fast and lightweight minimalistic object mapper generated on the fly
Key features
- Generation of mapping methods on the fly
- Reflection and expression trees are not used
- Performance like a hand-written mapper
- Minimum memory allocation
- Does not increase application startup time
- No dependencies in the final assembly
- No third party tools and IDE dependencies
- Static analysis support
- Code navigation support
- Easy to debug
- No attributes and fluid API
NextGenMapper is a tool that just solves a problem and tries not to create new ones
Usage
Add using NextGenMapper
and call the Map
extension method on the object you want to map
using NextGenMapper;
var source = new Source("Anton", 25);
var destination = source.Map<Destination>();
Console.WriteLine(destination);
record Source(string Name, int Age);
record Destination(string Name, int Age);
To customize the mapping of certain properties, call the MapWith
method and pass the value of the overridden property as an argument
using NextGenMapper;
var source = new Source("Anton", "Ryabchikov", 25);
var destination = source.MapWith<Destination>(name: source.FirstName + ' ' + source.LastName);
Console.WriteLine(destination);
record Source(string FirstName, string LastName, int Age);
record Destination(string Name, int Age);
In order for NextGenMapper to use your mapping when mapping other objects, you need to create a partial class Mapper
in the NextGenMapper
namespace and add the Map
method with your implementation to it
namespace NextGenMapper;
internal static partial class Mapper
{
internal static Destination Map<To>(this Source source)
=> source.MapWith<Destination>(name: source.FirstName + ' ' + source.LastName);
}
The following collection types are currently supported: List<T>
, Array<T>
, ICollection<T>
, IEnumerable<T>
, IList<T>
, IReadOnlyCollection<T>
, IReadOnlyList<T>
, ImmutableArray<T>
, ImmutableList<T>
, IImmutableList<T>
var sourceCollection = new List<Source> { new("Anton", 25) };
var destination = sourceCollection.Map<List<Destination>>();
Enums can also be mapped
var source = Source.EnumValue;
var destination = source.Map<Destination>();
Projection for IQueryable supported
_dbContext.Users.Project<UserDestination>().ToList();
Note: Due to the use of new technology, some versions of Visual Studio can sometimes experience problems with syntax highlighting if IntelliCode says an error, but the solution was build without errors is to simply restart Visual Studio
Installation
Install from the package manager console:
PM> Install-Package NextGenMapper -prerelease
Or from the .NET CLI as:
dotnet add package NextGenMapper --prerelease
How it works?
NextGenMapper uses the new C# language feature - Source Code Generators. You can describe the work of the Source Code Generator in the following steps:
- Code compiles
- The source code generator analyzes the assembly
- Generates new code based on analysis
- Compiles the new code and adds it to the assembly
This is how the method that is called initially looks like:
internal static To Map<To>(this object source) => throw new InvalidOperationException($""Error when mapping {source.GetType()} to {typeof(To)}, mapping function was not found. Create custom mapping function."");
When we call it, the generator analyzes this call and generates a mapping function:
internal static Destination Map<To>(this Source source)
=> new Destination(source.Name, source.Age);
The trick is that the method signatures are identical, but the generated method has more specific parameters and fits better, so it is called (this behavior is described in the specification)
Status
At the moment, all the main functionality has been added. But the work isn't over yet.
All tasks and their progress can be viewed on the project board
About
Automating generating mapping between classes
How to use
Example ( source csproj, source files )
- CSharp Project
- Program.cs
- Person.cs
- PersonDTO.cs
This is the CSharp Project that references NextGenMapper
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NextGenMapper" Version="0.1.0-alpha.13" OutputItemType="Analyzer" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>
This is the use of NextGenMapper in Program.cs
// See https://aka.ms/new-console-template for more information
using NextGenMapperDemo;
using NextGenMapper;
//var source = new Source("Anton", 25);
//var destination = source.Map<Destination>();
//Console.WriteLine(destination);
//record Source(string Name, int Age);
//record Destination(string Name, int Age);
Person p = new();
p.Name = "Andrei Ignat";
p.Country_Name = "Romania";
var dto = p.MapWith<PersonDTO>(
BirthCountry:new Country()
{
CountryCode=p.Country_CountryCode,
Name=p.Country_Name
});
//Name is automatically mapped
Console.WriteLine(dto.Name);
Console.WriteLine(dto.BirthCountry!.Name);
This is the use of NextGenMapper in Person.cs
namespace NextGenMapperDemo;
internal class Person
{
public int ID { get; set; }
public string? Name { get; set; }
public string? Country_Name { get; set; }
public string? Country_CountryCode { get; set; }
}
This is the use of NextGenMapper in PersonDTO.cs
namespace NextGenMapperDemo;
internal class Country
{
public string? Name { get; set; }
public string? CountryCode { get; set; }
}
internal class PersonDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public Country? BirthCountry { get; set; }
}
Generated Files
Those are taken from $(BaseIntermediateOutputPath)\GX
- MapperExtensions.g.cs
- Mapper_ConfiguredMaps.g.cs
- Mapper_ConfiguredMaps_MockMethods.g.cs
- StartMapper.g.cs
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace NextGenMapper.Extensions
{
internal static class MapperExtensions
{
/// <summary>
/// Do not use this method, for auto-generated mapper only!
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetSpan<TSource>(this IEnumerable<TSource> source, out ReadOnlySpan<TSource> span)
{
bool result = true;
if (source.GetType() == typeof(TSource[]))
{
span = Unsafe.As<TSource[]>(source);
}
#if NET5_0_OR_GREATER
else if (source.GetType() == typeof(List<TSource>))
{
span = CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source));
}
#endif
else
{
span = default;
result = false;
}
return result;
}
}
}
#nullable enable
using NextGenMapper.Extensions;
namespace NextGenMapper
{
internal static partial class Mapper
{
internal static NextGenMapperDemo.PersonDTO MapWith<To>
(
this NextGenMapperDemo.Person source,
NextGenMapperDemo.Country BirthCountry
)
=> new NextGenMapperDemo.PersonDTO
{
Name = source.Name,
BirthCountry = BirthCountry
};
}
}
#nullable enable
using NextGenMapper.Extensions;
namespace NextGenMapper
{
internal static partial class Mapper
{
internal static NextGenMapperDemo.PersonDTO MapWith<To>
(
this NextGenMapperDemo.Person source,
int Id = default!,
string? Name = default!,
NextGenMapperDemo.Country? BirthCountry = default!
)
{
throw new System.NotImplementedException("This method is a mock and is not intended to be called");
}
}
}
using System;
using System.Linq;
namespace NextGenMapper
{
internal static partial class Mapper
{
internal static To Map<To>(this object source) => throw new InvalidOperationException($"Error when mapping {source.GetType()} to {typeof(To)}, mapping function was not found. Create custom mapping function.");
internal static To MapWith<To>(this object source) => throw new InvalidOperationException($"Error when mapping {source.GetType()} to {typeof(To)}, mapping function was not found. Create custom mapping function.");
internal static To Project<To>(this IQueryable<object> source) => throw new InvalidOperationException($"Error when project {source.GetType()} to {typeof(To)}, project function was not found.");
internal static To ProjectWith<To>(this IQueryable<object> source) => throw new InvalidOperationException($"Error when project {source.GetType()} to {typeof(To)}, project function was not found.");
internal static To Project<To>(this IQueryable source) => throw new InvalidOperationException($"Error when project {source.GetType()} to {typeof(To)}, projection for non generic IQueryable is not supported");
internal static To ProjectWith<To>(this IQueryable source) => throw new InvalidOperationException($"Error when project {source.GetType()} to {typeof(To)}, projection for non generic IQueryable is not supported");
}
}
Usefull
Download Example (.NET C# )
Share NextGenMapper
https://ignatandrei.github.io/RSCG_Examples/v2/docs/NextGenMapper