Nel mondo dello sviluppo software moderno, in particolare con C# e .NET, la mappatura degli oggetti è una task comune ma spesso ripetitiva. Ci troviamo costantemente a trasformare dati da un formato all’altro: da entità di database a DTO (Data Transfer Object), da modelli di vista a entità di dominio, e così via. Questo processo, se gestito manualmente, può portare a boilerplate code, errori e difficoltà di manutenzione. È qui che entra in gioco Mapperly, una libreria innovativa che mira a semplificare drasticamente la mappatura degli oggetti in C#.
Che cos’è Mapperly?
Mapperly è un generatore di sorgenti (source generator) per C# che automatizza la creazione di mappatori di oggetti. A differenza di altre librerie di mappatura popolari come AutoMapper o Mapster, che si basano sulla reflection o sulla compilazione in fase di runtime, Mapperly genera il codice di mappatura in fase di compilazione. Questo significa che il codice generato è ottimizzato, tipizzato staticamente e non introduce alcun overhead di runtime. Il risultato? Prestazioni eccellenti e completa integrazione con il compilatore C#, fornendo un feedback immediato su eventuali errori di mappatura.
Perché Scegliere Mapperly? I Vantaggi Principali
Mapperly si distingue per una serie di vantaggi significativi che lo rendono una scelta attraente per molti sviluppatori:
- Zero Runtime Overhead: Poiché il codice di mappatura è generato in fase di compilazione, non ci sono costi di performance aggiuntivi durante l’esecuzione dell’applicazione. Il codice generato è semplicemente codice C# diretto.
- Strongly Typed (Fortemente Tipizzato): Ogni mappatura è verificata dal compilatore. Questo elimina errori di typo o mappature incomplete che potrebbero passare inosservate con librerie basate sulla reflection fino al runtime. Se un campo non corrisponde o un tipo è errato, il compilatore ti avviserà immediatamente.
- Ottime Prestazioni: Grazie alla generazione del codice, Mapperly produce mappatori altamente ottimizzati che spesso superano le prestazioni di librerie basate sulla reflection o sulla compilazione just-in-time (JIT) al primo utilizzo.
- Facilità d’Uso: La configurazione di base è incredibilmente semplice. Nella maggior parte dei casi, è sufficiente definire un’interfaccia o una classe parziale e annotarla con un attributo per far sì che Mapperly faccia la sua magia.
- Integrazione con IDE: Essendo un source generator, Mapperly si integra perfettamente con Visual Studio, Rider e altri IDE. Gli errori di compilazione relativi alla mappatura sono mostrati in tempo reale, proprio come qualsiasi altro errore di codice.
- Manutenzione Semplificata: Meno boilerplate code significa meno codice da mantenere e meno opportunità per introdurre bug. Le modifiche ai modelli di dati spesso richiedono solo piccoli aggiustamenti alle definizioni del mappatore.
- Debugging Semplificato: Poiché il codice è generato e visibile, puoi facilmente eseguire il debug del processo di mappatura passo dopo passo, cosa che non è sempre intuitiva con le librerie basate sulla reflection.
Come Iniziare con Mapperly: Un Esempio Pratico
Iniziare a usare Mapperly è piuttosto semplice. Vediamo un esempio pratico.
1. Installazione
Per prima cosa, aggiungi il pacchetto NuGet Riok.Mapperly al tuo progetto:
Bash
dotnet add package Riok.Mapperly
2. Definizione dei Modelli
Supponiamo di avere due classi: un’entità UserEntity e un DTO UserDto.
// Entità del database
public class UserEntity
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public DateTime DateOfBirth { get; set; }
public AddressEntity Address { get; set; } // Nested object
}
public class AddressEntity
{
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
// DTO per l'esposizione esterna
public class UserDto
{
public int Id { get; set; }
public string FullName { get; set; } // Combinazione di FirstName e LastName
public string Email { get; set; } // Mappatura da EmailAddress
public DateOnly BirthDate { get; set; } // Mappatura da DateTime a DateOnly
public AddressDto UserAddress { get; set; } // Nested object
}
public class AddressDto
{
public string StreetName { get; set; } // Mappatura da Street
public string CityName { get; set; } // Mappatura da City
public string ZipCode { get; set; } // Mappatura da PostalCode
public string CountryCode { get; set; } // Mappatura da Country
}
3. Creazione del Mapper
Ora, creiamo un’interfaccia (o una classe parziale) e annotiamola con l’attributo [Mapper]:
using Riok.Mapperly.Abstractions;
[Mapper]
public partial interface IUserMapper
{
// Mappatura da UserEntity a UserDto
UserDto Map(UserEntity userEntity);
// Mappatura da AddressEntity a AddressDto
// Mapperly riconosce automaticamente che deve mappare questo tipo annidato
AddressDto Map(AddressEntity addressEntity);
// Esempio di mappatura custom per FullName e BirthDate
[MapProperty(nameof(UserEntity.EmailAddress), nameof(UserDto.Email))]
[MapProperty(nameof(UserEntity.DateOfBirth), nameof(UserDto.BirthDate))]
partial UserDto MapWithCustomizations(UserEntity userEntity);
// Metodo per combinare nome e cognome
[UserMapping(Ignore = true)] // Ignora questa proprietà per la mappatura automatica
public string MapFullName(string firstName, string lastName) => $"{firstName} {lastName}";
// Metodo per convertire DateTime a DateOnly
[UserMapping(Ignore = true)] // Ignora questa proprietà per la mappatura automatica
public DateOnly MapDateOfBirthToDateOnly(DateTime dateOfBirth) => DateOnly.FromDateTime(dateOfBirth);
}
Spiegazione:
- L’attributo
[Mapper]indica a Mapperly di generare il codice per questa interfaccia. - Il metodo
Map(UserEntity userEntity)indica a Mapperly di creare una mappatura daUserEntityaUserDto. Mapperly tenterà di mappare automaticamente le proprietà con lo stesso nome. - Per le proprietà con nomi diversi (
EmailAddressaEmail,DateOfBirthaBirthDate), usiamo l’attributo[MapProperty]. - Per
FullName, che è una proprietà composita, Mapperly cerca un metodoMapFullNameche accetti i tipi delle proprietà sorgenti (string firstName,string lastName) e restituisca il tipo della proprietà di destinazione (string). In questo caso, abbiamo fornito noi il metodo. - Per
BirthDate, analogamente, abbiamo fornitoMapDateOfBirthToDateOnlyper la conversione. - L’attributo
partialsulla classe/interfaccia è fondamentale, in quanto permette a Mapperly di estendere la nostra definizione con il codice generato.
4. Utilizzo del Mapper
Ora puoi usare il tuo mappatore in qualsiasi parte del codice:
public class UserService
{
private readonly IUserMapper _userMapper;
public UserService(IUserMapper userMapper)
{
_userMapper = userMapper;
}
public UserDto GetUserDto(UserEntity userEntity)
{
return _userMapper.Map(userEntity);
}
public UserDto GetUserDtoWithCustomizations(UserEntity userEntity)
{
return _userMapper.MapWithCustomizations(userEntity);
}
public void ExampleUsage()
{
var addressEntity = new AddressEntity
{
Street = "Via Roma",
City = "Milano",
PostalCode = "20100",
Country = "Italy"
};
var userEntity = new UserEntity
{
Id = 1,
FirstName = "Mario",
LastName = "Rossi",
EmailAddress = "mario.rossi@example.com",
DateOfBirth = new DateTime(1990, 5, 15),
Address = addressEntity
};
var userDto = _userMapper.Map(userEntity);
Console.WriteLine($"User DTO (automatic): {userDto.FullName}, {userDto.Email}, {userDto.BirthDate}");
Console.WriteLine($"Address DTO (automatic): {userDto.UserAddress.StreetName}, {userDto.UserAddress.CityName}");
var userDtoCustom = _userMapper.MapWithCustomizations(userEntity);
Console.WriteLine($"User DTO (custom): {userDtoCustom.FullName}, {userDtoCustom.Email}, {userDtoCustom.BirthDate}");
}
}
In Visual Studio o Rider, espandendo i nodi del progetto in Esplora soluzioni, potresti trovare il codice generato da Mapperly sotto Dependencies -> Analyzers -> Riok.Mapperly -> Riok.Mapperly.MapperGenerator.
Questo codice è completamente leggibile e debuggabile.
Funzionalità Avanzate di Mapperly
Mapperly offre molte funzionalità avanzate per gestire scenari di mappatura più complessi:
- Mappatura di Collezioni: Mapperly supporta automaticamente la mappatura di
List<T>,IEnumerable<T>,Array, e altri tipi di collezioni. - Mappatura Condizionale: Puoi specificare condizioni per la mappatura di proprietà usando l’attributo
[MapProperty]con la proprietàCondition. - Ignorare Proprietà: L’attributo
[UserMapping(Ignore = true)]o[MapProperty(Ignore = true)]può essere usato per ignorare proprietà specifiche durante la mappatura. - Mappatura di Enum: Supporto per la mappatura di tipi
enum. - Mappatura di Tipi Annidati: Come visto nell’esempio, Mapperly gestisce automaticamente la mappatura di oggetti complessi annidati.
- Customizzazione dei Nomi: Regole di naming personalizzate per la mappatura delle proprietà.
- Inversion of Control (IoC) Container Integration: Mapperly può essere facilmente integrato con container IoC come .NET’s built-in DI, Autofac, ecc., registrando il tuo mappatore come singleton o scoped.
Mapperly vs. Altre Librerie di Mappatura
È utile confrontare Mapperly con altre soluzioni popolari per la mappatura degli oggetti in C#:
| Caratteristica | Mapperly | AutoMapper | Mapster |
| Tipo di Generazione | Source Generator (Compile-time) | Reflection (Runtime) o Expression Trees (JIT) | Expression Trees (JIT) |
| Performance | Eccellente (codice C# diretto) | Buona (ma con un piccolo overhead di reflection) | Molto buona (ma con un overhead iniziale di JIT) |
| Feedback Errori | Compilazione (Strongly Typed) | Runtime (se non configurato correttamente) | Runtime (se non configurato correttamente) |
| Configurazione | Attributi e metodi espliciti | Convenzioni e configurazione fluente | Convenzioni e configurazione fluente |
| Debuggabilità | Ottima (codice generato visibile) | Moderata (astrazione della reflection) | Moderata (astrazione delle expression trees) |
| Dipendenze Runtime | Nessuna dipendenza runtime per la mappatura | Dipendenza da AutoMapper | Dipendenza da Mapster |
| Curva di Apprendimento | Bassa per le basi, un po’ più alta per scenari complessi | Moderata per le basi, più alta per scenioni complessi | Bassa per le basi, un po’ più alta per scenioni complessi |
Quando usare Mapperly:
- Quando la performance è critica e vuoi evitare qualsiasi overhead di runtime.
- Quando vuoi un feedback immediato sugli errori di mappatura in fase di compilazione.
- Quando preferisci un approccio “convention over configuration” ma con la possibilità di personalizzare facilmente le mappature complesse.
- Quando lavori in un ambiente che beneficia della generazione di codice (ad esempio, ambienti serverless dove ogni millisecondo conta).
Quando potrebbero essere considerate altre librerie:
- AutoMapper: Se hai un progetto legacy che lo usa già, o se hai esigenze di mappatura molto dinamiche e complesse che beneficiano della sua configurazione flessibile basata sulla reflection.
- Mapster: Offre un buon equilibrio tra configurazione e performance, simile a Mapperly in termini di prestazioni ma con un approccio leggermente diverso alla generazione.
Considerazioni Finali
Mapperly rappresenta un passo avanti significativo nella mappatura degli oggetti in C#. Sfruttando la potenza dei source generator, offre una soluzione che combina le migliori prestazioni con una eccellente esperienza di sviluppo, riducendo il rischio di errori e il codice boilerplate.
Se stiamo cercando un modo efficiente e moderno per gestire le tue esigenze di mappatura in un’applicazione .NET, Mapperly merita sicuramente di essere considerato e provato. La sua semplicità d’uso e i chiari vantaggi in termini di prestazioni e affidabilità lo rendono uno strumento prezioso nel toolbox di qualsiasi sviluppatore C#.