Extending the Mapping Application Block
The Mapping Application Block handles two essential components:
Mappers and
Maps and currently supports two types of mapper: a
Basic Mapper for single data source entity to single domain object mapping, and a
Super Mapper for single data source entity to multiple domain object super type mapping. There are other mapping schemes that may be required like multiple data storage entities into single domain object mapping, such that it is quite likely that the MAB may need to be extended in time. The key to extending the MAB is to create a custom mapper for new mapping schemes, and custom maps for new map types.
Creating Custom Mappers
Creating a custom mapper is best achieved by deriving from the
MapperBase base class, or even a higher class such as
BasicMapper perhaps? At the very least you will need to implement the
IMapper interface. The real work for you as the extension developer is to handle the mechanics of the
ToDomainObject<T>(),
ToDomainObjectCollection<T>(),
FromDomainObject() and the
FromDomainObjectCollection() methods. The following code segment shows the
IMapper interface:
[C#]
public interface IMapper
{
#region Properties
/// <summary>
/// Gets the mapper name.
/// </summary>
/// <value>The name.</value>
string Name
{
get;
}
/// <summary>
/// Gets the table name.
/// </summary>
/// <value>The table name.</value>
string TableName
{
get;
}
/// <summary>
/// Gets the map dictionary.
/// </summary>
/// <value>A dictionary of maps.</value>
MapDictionary Maps
{
get;
}
/// <summary>
/// Gets or sets the instrumentation provider.
/// </summary>
/// <value>The instrumentation provider.</value>
MappingInstrumentationProvider InstrumentationProvider
{
get;
}
#endregion
#region Public Methods
/// <summary>
/// Maps a data transfer object to a single domain object.
/// </summary>
/// <typeparam name="T">The domain object type</typeparam>
/// <param name="dataTransferObject">The data transfer object.</param>
/// <returns>A populated domain object</returns>
/// <remarks>
/// <para>If the table name is null then the first table in the DataSet is used.</para>
/// <para>If there are multiple rows in the table then only the first row is considered.</para>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
T ToDomainObject<T>(DataSet dataTransferObject) where T : class;
/// <summary>
/// Maps a data transfer object to a collection of domain objects.
/// </summary>
/// <typeparam name="T">The domain object type</typeparam>
/// <param name="dataTransferObject">The data transfer object.</param>
/// <returns>A collection of populated domain objects</returns>
/// <remarks>
/// <para>If the table name is null then the first table in the DataSet is used.</para>
/// <para>Each row in the table is mapped to a domain object.</para>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
Collection<T> ToDomainObjectCollection<T>(DataSet dataTransferObject) where T : class;
/// <summary>
/// Maps a single domain object to a data transfer object.
/// </summary>
/// <typeparam name="T">The domain object type</typeparam>
/// <param name="domainObject">The domain object.</param>
/// <returns>A populated data transfer object</returns>
/// <remarks>
/// <para>If the table name is null then the table in the DataSet will be called the same as the domain object type.</para>
/// <para>The table will contain just one row.</para>
/// </remarks>
DataSet FromDomainObject<T>(T domainObject) where T : class;
/// <summary>
/// Maps a collection of domain objects to a data transfer object.
/// </summary>
/// <typeparam name="T">The domain object type</typeparam>
/// <param name="domainObjectCollection">The domain object collection.</param>
/// <returns>A populated data transfer object</returns>
/// <remarks>
/// <para>If the table name is null then the table in the DataSet will be called the same as the domain object type.</para>
/// <para>The table will contain a row for each domain object in the collection.</para>
/// <para>The domain object collection must contain only one type (or supertype set).</para>
/// </remarks>
DataSet FromDomainObjectCollection<T>(Collection<T> domainObjectCollection) where T : class;
#endregion
}
Note1: The
ToDomainObject<T>() method must always return a domain object of type
T, even if no property mapping ocurred or null if no rows were found in the DataSet. The
ToDomainObjectCollection<T>() method returns a
Collection<T> which can be empty if no rows were found in the DataSet.
Note2: The
FromDomainObject<T>() and
FromDomainObjectCollection<T>() methods must always return a
DataSet, even an empty one, if necessary, in the case when no objects are contained in the object collection.
Creating Custom Maps
For most scenarios the generic
Map is all that is required, which simply maps from a domain object public field or property name to a column name, for situations when something a little more sophisticated is required then you should implement the
IMap interface or even derive from the simple
Map class itself:
[C#]
public interface IMap
{
/// <summary>
/// Gets the map name.
/// </summary>
/// <value>The name.</value>
string Name
{
get;
}
/// <summary>
/// Gets or sets the column name of a table in a dataset.
/// </summary>
/// <value>The column name.</value>
/// <remarks>This is the name given to a table column within a DataSet,
/// where the DataSet type is acting as a Data Transfer Object.
/// If this column name is blank then the map name is used instead.</remarks>
string ColumnName
{
get;
set;
}
}
Note: Map classes are simple data classes and require little or no processing. You will receive instances of these types as an
IMap through the
MapDictionary so you will need to cast them in your mapper type to get at any specific properties other than
Name and
ColumnName.
Configuring your Custom Extension
See the section on
Configuration for details on how to configure a
CustomMapper or a
CustomMap. The important thing to note is that all the maps that you need to pass to your custom mapper or map constructor, will be configured through the
Attributes collection:
Note: these custom attribute values are static. If you need different attribute value choices then you will need to implement a custom mapper or map for each attribute value choice.
Finally you must register your custom type. Click on the
ellipsis button next to the
TypeName property to load the
type selector, you will not be able to load any assembly that does not contain the appropriate custom type:

Now you are done configuring, you should be able to do mapping using your custom mapper provider and custom map types, if specified, in exactly the same way as any other mapper provider.