Mapping entities in C# is a common task when dealing with object-relational mapping (ORM) frameworks, such as Entity Framework. This process involves transforming data between different types, often between domain models and data transfer objects (DTOs). AutoMapper is a powerful and widely-used library that simplifies this mapping process, reducing boilerplate code and increasing productivity. In this article, we will explore how to use AutoMapper to map entities in a C# application, focusing on best practices and common scenarios.
What is AutoMapper?
AutoMapper is an open-source library for .NET that simplifies the mapping between two object models. It is particularly useful in scenarios where the structure of your domain models differs from the structure of your database entities or when you need to project data into a different shape.
Key features of AutoMapper include:
Convention-based Mapping: AutoMapper can automatically map properties with matching names between source and destination types based on naming conventions.
Explicit Configuration: For cases where naming conventions are not sufficient, you can configure mappings explicitly using a fluent API.
Flattening and Unflattening: AutoMapper supports flattening and unflattening complex object structures, which is useful when dealing with nested or hierarchical data.
Custom Value Resolvers: You can define custom logic to resolve values during the mapping process, allowing for more complex scenarios.
Getting Started with AutoMapper
To begin using AutoMapper, follow these steps:
Step 1: Install AutoMapper via NuGet
Install the AutoMapper NuGet package in your project using the Package Manager Console or the Visual Studio Package Manager:
Install-Package AutoMapper
Step 2: Configure AutoMapper
In your application startup code, typically in the Startup.cs
file for ASP.NET Core applications, configure AutoMapper by creating a profile and adding mappings:
using AutoMapper;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<SourceEntity, DestinationEntity>();
// Add more mappings as needed
}
}
// In your startup code (e.g., Startup.cs)
public void ConfigureServices(IServiceCollection services)
{
// Other configurations...
services.AddAutoMapper(typeof(MappingProfile));
// Other configurations...
}
Step 3: Inject IMapper where needed
In classes where you want to perform mapping, inject the IMapper
interface:
public class MyService
{
private readonly IMapper _mapper;
public MyService(IMapper mapper)
{
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public void PerformMapping()
{
var sourceEntity = new SourceEntity { /* Initialize properties */ };
var destinationEntity = _mapper.Map<DestinationEntity>(sourceEntity);
// Use the mapped entity as needed
}
}
Basic Mapping Scenarios
Let's explore some common mapping scenarios using AutoMapper.
1. Simple Property Mapping
public class SourceEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DestinationEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
2. Nested Property Mapping
public class SourceEntity
{
public int Id { get; set; }
public NestedEntity Nested { get; set; }
}
public class NestedEntity
{
public string Description { get; set; }
}
public class DestinationEntity
{
public int Id { get; set; }
public string NestedDescription { get; set; }
}
3. Ignore Property
public class SourceEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string IgnoreMe { get; set; }
}
public class DestinationEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
In the mapping profile:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(dest => dest.IgnoreMe, opt => opt.Ignore());
4. Custom Value Resolver
public class SourceEntity
{
public DateTime DateOfBirth { get; set; }
}
public class DestinationEntity
{
public int Age { get; set; }
}
In the mapping profile:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(dest => dest.Age, opt => opt.MapFrom<AgeResolver>());
And create the AgeResolver
:
public class AgeResolver : IValueResolver<SourceEntity, DestinationEntity, int>
{
public int Resolve(SourceEntity source, DestinationEntity destination, int destMember, ResolutionContext context)
{
var today = DateTime.Today;
var age = today.Year - source.DateOfBirth.Year;
// Adjust age if the birthday hasn't occurred yet this year
if (source.DateOfBirth.Date > today.AddYears(-age))
age--;
return age;
}
}
Advanced Mapping Configurations
AutoMapper provides advanced features to handle more complex scenarios.
1. Conditional Mapping
You can conditionally apply mappings based on specific criteria:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name == "John" ? "Special John" : src.Name));
2. Mapping Collections
Mapping collections involves configuring AutoMapper to map each element in the source collection to the corresponding element in the destination collection:
CreateMap<SourceEntity, DestinationEntity>()
.ForMember(dest => dest.Collection, opt => opt.MapFrom(src => src.Collection.Select(item => _mapper.Map<SourceItem, DestinationItem>(item))));
3. Inheritance Mapping
When dealing with inheritance hierarchies, you can configure AutoMapper to handle the mapping based on the derived type:
CreateMap<BaseEntity, BaseDto>()
.Include<DerivedEntity, DerivedDto>();
CreateMap<DerivedEntity, DerivedDto>();
4. Reverse Mapping
AutoMapper supports reverse mapping, allowing you to generate a mapping configuration in the opposite direction:
CreateMap<SourceEntity, DestinationEntity>();
CreateMap<DestinationEntity, SourceEntity>();
Conclusion
Using AutoMapper in your C# projects can significantly simplify the process of mapping entities, reducing the amount of boilerplate code and increasing maintainability. By following the steps outlined in this article and exploring the various mapping scenarios and configurations, you can leverage the full power of AutoMapper to handle complex mapping tasks efficiently.
Remember to check the official AutoMapper documentation for more in-depth information and advanced features. Happy mapping!