AgileMapper Documentation

repository·master·Indexed 19 days ago

https://github.com/agileobjects/agilemapper

A zero-configuration, highly-configurable object mapper for .NET 3.5+ and .NET Standard 1.0+. AgileMapper supports deep cloning, merging, updating, and query projection using methods like Map, ToANew, Project, and Over. It features flexible configuration through MapperConfiguration classes, assembly scanning for derived types, and support for a wide range of collection types including Arrays, List<T>, and HashSet<T>.

Tokens
37.1K
Snippets
124
Records
151
Agent score
66%

What's inside AgileMapper

  1. What is AgileMapper

    master
    AgileMapper is a zero-configuration, highly-configurable, and unopinionated object mapper for .NET. It is designed to handle complex object transformations including flattening, unflattening, deep cloning, merging, updating, and projecting queries. It targets .NET 3.5+ and .NET Standard 1.0+.
  2. Map ExpandoObjects to conditional derived types

    master

    You can configure an ExpandoObject to map to different types within a class hierarchy based on a condition. Use .If((source, target) => ...) to define the condition.

    Important: When writing the condition for a dynamic source, you must access the source as an IDictionary<string, object> (e.g., d["Key"]) because expression trees cannot contain dynamic member accesses.

    Mapper.WhenMapping
        .FromDynamics
        .To<Animal>()
        .If((d, a) => d["Type"] == AnimalType.Dog)
        .MapTo<Dog>()
        .And
        .If((d, a) => d["Type"] == AnimalType.Cat)
        .MapTo<Cat>();
    
    // Usage
    var animals = Mapper.Map(source).ToANew<List<Animal>>();
  3. How to use multiple IMapper instances with Dependency Injection

    master

    Because the Instance API uses IMapper, you can register different mapper instances for different controllers or services within your DI container (e.g., StructureMap). This allows you to have specialized mapping rules for different parts of your application.

    public class OrderMapperRegistry : Registry
    {
        public OrderMapperRegistry()
        {
            var mapperOne = Mapper.CreateNew();
            // Do mapperOne.WhenMapping configuration...
    
            // Register mapperOne as the instance to pass to OrderOneController:
            For<OrderOneController>().Use<OrderOneController>()
                .Ctor<IMapper>().Is(mapperOne);
    
            var mapperTwo = Mapper.CreateNew();
            // Do mapperTwo.WhenMapping configuration...
    
            // Register mapperTwo as the instance to pass to OrderTwoController:
            For<OrderTwoController>().Use<OrderTwoController>()
                .Ctor<IMapper>().Is(mapperTwo);
        }
    }
  4. Use Meta Members to map derived data

    master

    AgileMapper supports 'meta' members, which are destination properties that contain information derived from other source members. AgileMapper automatically populates these members based on naming conventions without requiring explicit mapping configuration. Meta members are also supported in query projections.

    class Account
    {
        public int Id { get; set; }
        public ICollection<Order> Orders { get; set; }
        public ICollection<Address> DeliveryAddresses { get; set; }
    }
    
    class AccountDto
    {
        public int Id { get; set; }
        public bool HasOrders { get; set; }
        public int DeliveryAddressCount { get; set; }
        public bool FirstDeliveryAddressHasPostcode { get; set; }
    }
    
    // AgileMapper will automatically populate:
    // - HasOrders: true if Orders is non-null and has count > 0
    // - DeliveryAddressCount: the count of DeliveryAddresses
    // - FirstDeliveryAddressHasPostcode: true if the first element in DeliveryAddresses has a non-default Postcode
  5. Map derived types automatically using naming conventions

    master

    AgileMapper supports automatic discovery and mapping of derived types based on naming conventions. For example, if you have a base type Person and a derived type Customer, and corresponding view models PersonViewModel and CustomerViewModel, AgileMapper will automatically pair Customer to CustomerViewModel even if the source variable is typed as the base Person.

    When mapping, use .ToANew<TTarget>() to ensure the mapper identifies and instantiates the correct derived target type.

    public class Person {}
    public class Customer : Person
    {
        public float Discount { get; set; }
    }
    
    public class PersonViewModel {}
    public class CustomerViewModel : PersonViewModel
    {
        public double Discount { get; set; }
    }
    
    // Usage:
    var person = new Customer { Discount = 0.1f } as Person;
    var viewModel = Mapper.Map(person).ToANew<PersonViewModel>();
    // viewModel is of type CustomerViewModel and Discount is 0.1
  6. How enums are mapped in AgileMapper

    master

    AgileMapper supports several ways to map enums based on the source type:

    • From other enums, strings, or objects: Matches by the enum member name (case-insensitive). If an object is used, it relies on a ToString() call.
    • From numeric values (int, long, decimal, etc.): Matches by the underlying numeric value of the enum member.

    Flags Enums Flags enums are automatically mapped from numeric, string, enum, and character source values. String sources can be a combination of comma-separated numeric values and enum member names, which AgileMapper will parse and map correctly.

  7. How AgileMapper identifies objects during updates and merges

    master

    When performing collection updates or merges, AgileMapper identifies existing objects to determine if they should be updated or if new ones should be added. By default, it looks for members with the following naming patterns:

    • Id
    • <Type name>Id (e.g., CustomerId for a Customer type)
    • Identifier
    • <Type name>Identifier (e.g., CustomerIdentifier for a Customer type)

    If your objects use different naming conventions for their unique identifiers, you must configure the mapper to use the correct member.

  8. Understand performance side effects of inline configuration

    master

    When an inline-configured mapping is performed for the first time, AgileMapper clones the existing configuration, combines it with the inline rules, and then caches the resulting combined configuration.

    Subsequent mappings using the same configuration incur a very small performance penalty to retrieve the cached version. While the overhead is minimal (e.g., ~0.02ms per call), it can accumulate in high-throughput scenarios (e.g., 1 million calls adding ~20 seconds of delay). If mapping performance is a critical priority, consider using pre-configured instance mappers instead of inline configuration.

  9. Order the application of MapperConfiguration classes

    master

    If multiple configuration classes define settings for the same types in an object graph, you may need to control the order in which they are applied.

    Use the [ApplyAfter(typeof(ConfigurationType))] attribute on your MapperConfiguration class to specify that it should be applied after a specific configuration. AgileMapper will follow chains of ApplyAfter attributes to ensure the correct sequence.

    Note: Defining circular references between configuration types will result in a MappingConfigurationException.

    // Configure aspects of Parent -> Parent mapping, which includes 
    // mapping Child -> Child. Automatically apply ChildMapperConfiguration,
    // then apply this configuration afterwards.
    [ApplyAfter(typeof(ChildMapperConfiguration))]
    public class ParentMapperConfiguration : MapperConfiguration
    {
    }
    
    // Configure aspects of Child -> Child mapping:
    public class ChildMapperConfiguration : MapperConfiguration
    {
    }