AgileMapper Documentation
repository·master·Indexed 19 days ago
https://github.com/agileobjects/agilemapperA 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>.
What's inside AgileMapper
- 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+.
Map ExpandoObjects to conditional derived types
masterYou can configure an
ExpandoObjectto 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>>();Compose complex Meta Members
masterMeta members can be combined to create complex logic. For example,FirstDeliveryAddressHasPostcodecombines theFirstDeliveryAddresslogic (accessing the first element of theDeliveryAddressescollection) with a check for theHasPostcodeproperty on that element.Use object factories for complex object construction
masterIf configuring multiple constructor parameters becomes complex or awkward, you can instead configure an object factory for a specific object type to handle its instantiation.How to use multiple IMapper instances with Dependency Injection
masterBecause 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); } }Use Meta Members to map derived data
masterAgileMapper 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 PostcodeMap derived types automatically using naming conventions
masterAgileMapper supports automatic discovery and mapping of derived types based on naming conventions. For example, if you have a base type
Personand a derived typeCustomer, and corresponding view modelsPersonViewModelandCustomerViewModel, AgileMapper will automatically pairCustomertoCustomerViewModeleven if the source variable is typed as the basePerson.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.1How enums are mapped in AgileMapper
masterAgileMapper 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.
- From other enums, strings, or objects: Matches by the enum member name (case-insensitive). If an object is used, it relies on a
How AgileMapper identifies objects during updates and merges
masterWhen 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.,CustomerIdfor aCustomertype)Identifier<Type name>Identifier(e.g.,CustomerIdentifierfor aCustomertype)
If your objects use different naming conventions for their unique identifiers, you must configure the mapper to use the correct member.
Understand performance side effects of inline configuration
masterWhen 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.
Order the application of MapperConfiguration classes
masterIf 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 yourMapperConfigurationclass to specify that it should be applied after a specific configuration. AgileMapper will follow chains ofApplyAfterattributes 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 { }How projected joined entities are ordered
masterWhen AgileMapper projects through a join entity to a joined type, it determines the order of the resulting collection using the following priority:
- A member named
Orderon the join entity. - A member named
DateCreatedon the join entity. - If neither exists, it defaults to using the identifier (e.g.,
Id) of the joined type.
- A member named