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:

Adding Custom Attributes

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:

Registering a Custom Mapper 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.

Last edited Jun 16, 2010 at 11:44 AM by ewdev, version 5

Comments

No comments yet.