From an application's point of view, mapping can be either inbound or outbound. Inbound mapping converts raw data (positional or delimited flat file or XML document) into objects that are native to the application. Naturally, outbound mapping represents the reverse operation: transformation of native objects (or losely typed datasets) into flat or XML data.
Although simple logic can be hard-coded inside application, this approach doesn't scale well. It's a good idea to have a framework that will allow new mapping logic to be put in place with little or no custom coding.
In order to define mapping, we need to specify mapping rules (which source data elements map to a destination element?) and, optionally, transformation (what needs to be done with source data elements in order to arrive to the destination element?). Here is how a single inbound mapping piece can be represented (fields are shown instead of properties for brevity):
public class InboundMappingPiece
{
public string PropertyName;
public MethodInfo MethodInfo;
public int StartIndex;
public int Length;
public string XPath;
}
Let's review the fields. PropertyName designates the "destination": which property of a native object we are populating with this mapping piece. MethodInfo is a function pointer that implements transformation logic. Now the only thing missing is the source element. In order to map from positional flat file, we need to know StartIndex and Length, while XML data is easily extracted using XPath queries.
public class OutboundMappingPiece
{
public string[] SourceElements;
public MethodInfo MethodInfo;
public string DestinationElement;
public int DestinationWidth;
public char PadCharacter;
}
OutboundMappingPiece is built in a similar fashion. We've got an array of SourceElements (in case we wanted to use many-to-one mapping), and a MethodInfo pointer for transformation logic. If our destination is XML, we need to know the name of DestinationElement, otherwise DestinationWidth and PadCharacter allow us to generate flat file output.
By matching lists of mapping pieces with type names, we can declare the map as a whole. This is how a Mapper class may look like:
public class Mapper
{
private Dictionary<String, List<InboundMappingPiece>> _InboundMap;
private Dictionary<String, List<OutboundMappingPiece>> _OutboundMap;
// Outbound
public string Transform(object obj) {...}
public string TransformToXml(object obj) {...}
// Inbound
public T GetObject<T>(string rawData) where T : new() {...}
public T GetObject<T>(XmlNode node) where T : new() {...}
}
Implementation of inbound and outbound transformation methods can be boiled down to iterating through lists of mapping pieces and applying them to the source data. Of course, the devil is in details, and there are lots of details to be considered: how to handle arrays, nullable types, nested types, and so on. Another interesting question is where to store mapping configuration, but I will leave it until the next post.
No comments:
Post a Comment