Copied to Clipboard
Configuration Caching
Mapping configurations are cached internally, so repeated mappings are efficient:
mapper.CreateMap<Source, Target>(mappings);
// Subsequent calls use cached configuration
for (int i = 0; i < 10000; i++)
{
mapper.Map<Source, Target>(sources[i]); // Fast
}
API Reference
IMapper Interface
public interface IMapper
{
TTarget Map<TSource, TTarget>(TSource source);
IEnumerable<TDestination> Map<TSource, TDestination>(IEnumerable<TSource> source);
}
Map Method (Single Object)
-
Parameters:
-
TSource source - The source object to map from
-
Returns:
TTarget - The mapped target object
-
Returns null if: Source is null
Map Method (Collection)
-
Parameters:
-
IEnumerable<TSource> source - The collection of source objects to map from
-
Returns:
IEnumerable<TDestination> - The mapped collection of target objects
-
Returns null if: Source is null
IMappingExpression Interface
public interface IMappingExpression<TSource, TTarget>
{
IMappingExpression<TSource, TTarget> ForMember<TValue>(
Expression<Func<TTarget, TValue>> targetMember,
Expression<Func<TSource, TValue>> sourceMember);
}
ForMember Method
- Configures custom mapping between properties
- Supports nested property paths
- Returns the expression for fluent chaining
MappingModule Abstract Class
public abstract class MappingModule<TSource, TTarget>
{
protected MappingModule(bool enableDefaultMapping = true);
public abstract Action<IMappingExpression<TSource, TTarget>> CreateMappings();
}
Constructor Parameters
-
enableDefaultMapping - When true, unmapped properties use name-based mapping
ServiceCollectionExtensions
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddTurboMapper(
this IServiceCollection services);
}
AddTurboMapper Method
- Registers IMapper as singleton
- Auto-discovers and registers mapping modules
- Returns IServiceCollection for chaining
PropertyMapping Class
internal class PropertyMapping
{
public string SourcePropertyPath { get; set; }
public string TargetPropertyPath { get; set; }
}
Best Practices
1. Use Dependency Injection
Always register TurboMapper with DI for automatic module discovery:
services.AddTurboMapper();
2. Organize with Mapping Modules
Group related mappings in modules:
// UserMappings.cs
internal class UserDTOMappingModule : MappingModule<UserDTO, UserEntity>
{
// ...
}
internal class UserEntityMappingModule : MappingModule<UserEntity, UserDTO>
{
// ...
}
3. Enable Default Mapping
Unless you need strict control, enable default mapping:
public MyMappingModule() : base(enableDefaultMapping: true)
{
// Only specify differences from default
}
4. Handle Null Values
Always check for null in business logic when needed:
var entity = mapper.Map<DTO, Entity>(dto);
if (entity?.Address == null)
{
entity.Address = new Address();
}
5. Use Type-Safe Expressions
Leverage lambda expressions for compile-time safety:
config.ForMember(dest => dest.FullName, src => src.FirstName)
.ForMember(dest => dest.Years, src => src.Age);
6. Test Your Mappings
Always unit test custom mappings:
[Test]
public void Map_UserDTOToEntity_MapsCorrectly()
{
var dto = new UserDTO { FirstName = "John", Age = 30 };
var entity = mapper.Map<UserDTO, UserEntity>(dto);
Assert.AreEqual("John", entity.Name);
Assert.AreEqual(30, entity.Years);
}
7. Avoid Deep Nesting
Keep object hierarchies reasonable for performance:
// Good - 2-3 levels
User.Address.City
// Potentially slow - 5+ levels
User.Profile.Settings.Preferences.Display.Theme
8. Reuse Mapping Configurations
Don't create mappings repeatedly:
// Good - Create once
mapper.CreateMap<Source, Target>(mappings);
// Bad - Inside loop
for (int i = 0; i < 1000; i++)
{
mapper.CreateMap<Source, Target>(mappings); // Don't do this
}
Troubleshooting
Common Issues
Properties Not Mapping
Problem: Properties with matching names aren't mapping.
Solutions:
- Ensure properties have public getters and setters
- Check property names match exactly (case-sensitive)
- Verify target property has a setter (
set accessor)
// Won't map - no setter
public class Target
{
public string Name { get; } // Read-only
}
// Will map - has setter
public class Target
{
public string Name { get; set; }
}
Nested Objects Are Null
Problem: Nested objects aren't being created.
Solutions:
- Check if source nested object is null
- Ensure nested class has parameterless constructor
- Verify property types match or are compatible
public class Address
{
// Must have parameterless constructor
public Address() { }
public string Street { get; set; }
}
Type Conversion Fails
Problem: Automatic type conversion not working.
Solutions:
- Check if types are compatible for conversion
- Implement explicit mapping for complex conversions
- Ensure enum names match string values (case-insensitive)
// Works - Compatible types
int age = 25;
string ageStr = age.ToString(); // TurboMapper handles this
// May fail - Incompatible types
object obj = new MyClass();
int value = (int)obj; // Need explicit handling
Mapping Module Not Found
Problem: Mapping module not being discovered.
Solutions:
- Ensure module class is not abstract
- Make module class internal or public (not private)
- Verify assembly is loaded in AppDomain
- Check that AddTurboMapper() is called
// Correct - Will be discovered
internal class MyMappingModule : MappingModule<Source, Target>
{
// ...
}
// Wrong - Won't be discovered
private class MyMappingModule : MappingModule<Source, Target>
{
// ...
}
Performance Issues
Problem: Mapping is slow with large datasets.
Solutions:
- Reuse mapper instance (singleton)
- Configure mappings once, not repeatedly
- Use Parallel processing for large collections
- Profile to identify specific bottlenecks
// Efficient approach
var mapper = serviceProvider.GetService<IMapper>();
var results = sources
.AsParallel()
.Select(s => mapper.Map<Source, Target>(s))
.ToList();
Debug Tips
Enable Detailed Exceptions
Wrap mapping calls to get better error messages:
try
{
var result = mapper.Map<Source, Target>(source);
}
catch (Exception ex)
{
Console.WriteLine($"Mapping failed: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
throw;
}
Verify Configuration
Test that your configuration is registered:
var mapper = serviceProvider.GetService<IMapper>();
var source = new Source { Name = "Test" };
try
{
var target = mapper.Map<Source, Target>(source);
Console.WriteLine("Mapping successful");
}
catch
{
Console.WriteLine("Mapping configuration missing");
}
Check Assembly Loading
Verify assemblies containing modules are loaded:
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
Console.WriteLine($"Loaded: {assembly.FullName}");
}
Examples
Example 1: Simple DTO Mapping
public class CreateUserRequest
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime CreatedAt { get; set; }
}
// Mapping
var request = new CreateUserRequest
{
FirstName = "John",
LastName = "Doe",
Email = "john@example.com"
};
var user = mapper.Map<CreateUserRequest, User>(request);
user.CreatedAt = DateTime.UtcNow;
Example 2: Complex Business Object
public class OrderDTO
{
public string OrderNumber { get; set; }
public CustomerDTO Customer { get; set; }
public List<OrderItemDTO> Items { get; set; }
}
public class OrderEntity
{
public string OrderNumber { get; set; }
public CustomerEntity Customer { get; set; }
public List<OrderItemEntity> Items { get; set; }
}
// Map with nested objects
var dto = GetOrderDTO();
var entity = mapper.Map<OrderDTO, OrderEntity>(dto);
// Map collection
entity.Items = dto.Items
.Select(item => mapper.Map<OrderItemDTO, OrderItemEntity>(item))
.ToList();
Example 3: Flattening Hierarchy
internal class OrderFlatteningModule : MappingModule<Order, OrderFlat>
{
public OrderFlatteningModule() : base(true)
{
}
public override Action<IMappingExpression<Order, OrderFlat>> CreateMappings()
{
return config =>
{
config.ForMember(dest => dest.CustomerName, src => src.Customer.Name)
.ForMember(dest => dest.CustomerEmail, src => src.Customer.Email)
.ForMember(dest => dest.ShippingStreet, src => src.ShippingAddress.Street)
.ForMember(dest => dest.ShippingCity, src => src.ShippingAddress.City);
};
}
}
Version History
v1.2.0 - Enhanced Core & Mapping Features (Latest)
-
Performance Improvements: Significant performance enhancements (2x+) through compiled expression trees and metadata caching
-
Collection Mapping Support: Added Map method overload to IMapper for collection mapping operations that returns IEnumerable
-
Ignored Properties Option: Added Ignore method to IMappingExpression to skip properties during mapping
-
Custom Type Converters Registration: Added RegisterConverter method to MappingModule for custom type conversion functions that can be registered with type mappings
-
Improved Nullable Type Handling: Enhanced ConvertValue method to handle nullable types properly
-
Conditional Mapping: Added When method to IMappingExpression for conditional property mapping
-
Mapping Transformations: Added MapWith method for transformation functions during mapping
-
Comprehensive Type Conversions: Enhanced ConvertValue with DateTime, TimeSpan, and other common type conversions
-
Configuration Validation: Added ValidateMapping method to IMapper that returns ValidationResult for early validation
-
Improved Error Messages: Better debugging information for conversion failures
-
Refactored Code: Consolidated duplicate GetMemberPath methods into shared internal implementation
-
Optimized Object Creation: Replaced Activator.CreateInstance with factory delegates for faster instantiation
-
Configuration Caching: Added caching for mapping configurations and lookups for faster performance
-
Backward Compatibility: Maintains full backward compatibility with v1.0.0
v1.0.0
- Initial release
- Default name-based mapping
- Custom property mapping
- Nested object support
- Type conversion
- Mapping modules
- Dependency injection integration
- Multi-framework support
License
TurboMapper is licensed under the terms specified in the LICENSE file.
Support
For issues, questions, or contributions:
Contributing
Contributions are welcome! Please submit pull requests or open issues on GitHub.