Overview | Data Query | Modeling | Mapping
The mapping layer contains support for the complex mapping interactions faced in most applications. Boost takes a declarative approach for defining these interactions and uses the features found in C# 3.0 liberally. The mapping contracts exposed use delegates defined in the System namespace with Action<TInput, TOutput> serving as the glue. In addition, the defaults can be fully overridden, not a single class/interface has to remain. Best of all, the entire mapping layer can be removed, not a single interface is mandatory in the Boost Linq implementation. Furthermore, the existing interfaces and factory/helper methods (Extension methods) can be replaced on as per needed basis (hybrid mode) or a provider can choose to replace.

A Unique Approach

Many frameworks, including commercial ORMs, either use extensive code generation, subclassing or interfaces for transformations. For most programming tasks that may be a reasonable approach, but with so many different storage strategies, schema architectures and coding practices it would be impossible to begin to support a universal model. Letting the user define such transformations was the only vaible choice. Every software component should create API’s that are consumed by at least 1 client. With this in mind, a fully implemented mapping stack is supplied as part of the core framework. This stack can serve as both a starting point or for learning purposes. Whichever the case, applications can be created quickly and easily without the need to spend lots of hours creating custom approaches.

A Lesson from Linq

An interesting api lessons exhibited in Linq was interfaces with implementations, and a seemingly new language that mimicked other popular ones. And best of all, all of the machinery was replaceable. As a result, much of the default mapping functionality is implemented as Extension Methods over Interfaces using existing delegates as the contract. Additionally, lambda syntax is well suited to the “template like” semantics that mapping exhibit. The use of lambda declarations along with the fluent interface shorten the amount of code the user needs to write.

Helpers to the rescue

Rather than complicating even the interfaces that are shipped and used as part of the defaults.
[Remember the contracts for mapping are System delegates Action and Converter]

Helpers were added as extension methods as this felt like the natural approach to enable extensibility scenarios with minimum code additions.
• Boost ships default implementations of the Interfaces, by extending the construction and composition of mappers via Extension methods, some or all of these default class implementations can be removed and both the interfaces and helpers would still work.
• Further more, new helpers can be developed and used, replacing these while allowing the previous scenario, a mixing or discarding the mapping classes.
• Finally all can be discarded in choice of another methodology.

Mappers in action

var mapper = ...
mapper.Fields((c1, c2 ) => c1.Prop1 = c2.Prop1)
	.Fields((c1, c2 ) => c1.Prop2 = c2.Prop2)
	.Fields((c1, c2 ) => c1.Prop2 = c2.Prop2)
	.Reference(c => c.RefProperty, refMapper)
	.ReferenceList( c => c.MyCollection, colMapper);

[This scenario covers simple (primitive types) as well as Reference types with an accessible property (setter) and a collection type that implements ICollection. The “Add” method is then called and newly mapped objects are aggregated.]

As demonstrated above, even complicated mappings can be described with a couple simple parameters rather many objects or lengthy descriptions.

This magic is achieved achieved by convention, where the 2 objects being mapped have the same property names.

Alternatively further refinement is done by supplying additional information.
var mapper = ...
mapper.Fields((c1, c2 ) => c1.Prop1 = c2.Prop1)
	.Fields((c1, c2 ) => c1.Prop2 = c2.Prop2)
	.Fields((c1, c2 ) => c1.Prop2 = c2.Prop2)

(1) .Reference(c => c.RefProperty, refMapper).Setter((o,p)=> o.Prop = p) // Explicit setter.
(2) .ReferenceList( c => c.MyCollection, colMapper, () => new Reference()); // Provide a constructor method.
1. A setter method is defined, which will be used to set the mapped Reference Object.
2. A construction method Factory is defined which will be used to instanciate the mapped objects to be added to the collection.

Digging Deeper

Every mapping function is treated equally, even in composition and thus the following statement is true.

FieldMaps = Execute.

Because of this fact, Execute methods become compose-able. This can be added to the FieldMap variable utilizing the existing “+=” operators on Delegate types.

Whether implementing additional helper methods, replacing existing ones, or using the IMapper family of interfaces, attempts should be made to append to the FieldMaps rather than implementing further collection semantics unless there is an unavoidable reason.

Even non trivial issues like setting properties of reference types disappear, and become subtle conventions.

Using a pure Interface based approach, exposing state, overlaying implementation, construction via FactoryMethod:
Exposing state is needed to make it more testable instead of marker interfaces (see ASP.NET and to a certain extent Linq). While this may raise complexity, it also more openly allows construction of interfaces without included extension methods. Making the API more transparent.

Object Construction During Mapping

At times it is necessary to create new instances of objects to add to a collection, or set properties. For value types it is encouraged to implement implicit casting to aid in setters. For references however, this can be a challenge. In order to maintain convention yet allow for extension, there are 3 approaches enabled out of the box.

1. Helper methods enforce the “:new()” generic constraint to minimize reflection cost.
2. Activator <T> creates a new instance where T is the desired mapped type.
3. Supply a parameterless method (delegate) that creates a new instance.

As one can see these are reasonable cost for gradual extension.

Auto Mapping

The auto mapping feature allows reduction of property accessor definition by simply calling the “AutoMap” method (Extension method) on an IMapper.
var mapper = ...

Internally, properties are enumerated and registered using the same process as manual mapping would have. This works as long as the convention for naming the attributes with the same name is followed between two objects being mapped.

Do call this method first before additional map registration.
Consider that nothing prevents code from registering the same attribute more than once, extra care must be taken when using Auto Mapping.
Current implementation relies on case sensitive Property Name match.
This is a new feature and not fully vetted. Some unit test coverage. Feature does not stabilize mapping as it is a layer on top and using some new Linq expression magic.

Why delegates again?
1. Boost relies on delegates already
2. Delegates are as loose of a contract as is currently allowed in the CLR.
3. Yields much better performance compared to Reflection.

The alternatives would have been code generation or reflection.
Code generation leads to the following:
1. Re-generation as things change
2. Code bloat
3. Brittle maintenance
4. Could create bigger breaks in future versions.

Some historical context for reference.
Following an accidental encounter with a blog post that seemed to have similar concerns relating to mapping. In this case, as it related to exposing DTO’s as POCOs rather than business objects as well as mentioning a framework. The AutoMapper ( framework currently has eerily similar public API. However the implementation seems bloated and reflection heavy.

More history...
Whilst the motivating factor was adherence to the DRY principle by doing away with most of the scalar property defining. Several bloggers deserve mention for inspiration as well as insight into how delegates work. There are some benchmarks as well included as reference.

Overview | Data Query | Modeling | Mapping

Last edited Feb 10, 2010 at 9:33 PM by adev, version 5


No comments yet.