This code comes as a result of a prototype project to find a method of mapping data between the relational database representation and the object representation. The thought process went something like this:
- ADO.NET can represent its objects as XML
- C# makes it easy to serialize objects (well designed ones at least) to XML
- XSLT gives a straightforward way of transforming one XML structure to another
Not the most complicated of thought processes to follow (fortunately).
So the idea was to write some code to map relational data structures from ADO.NET into the object oriented data representation using XSLT. We wanted to work with an OO model to make the coding of the business objects cleaner, and to work with a relational database for persistence. The thought was that the ADO.NET objects should allow us to persist to the database with relative ease, or at least have dealt with lots of the problems.
Excuses 'n stuff...
So I said this was a play project, right? Some excuses up front:
- It's all in a single console app.
- It all has a root namespace of
- The ZIP contains loads of random classes created for various other ideas (messy).
Main entry point method contains the test bed code (that we will walk through here) along with loads of other random test bed code.
- Plenty of cheese to be found within the code (I will highlight some as we go through, the rest you can find).
This example is based on the ubiquitous Northwind database and is set up (with the hard coded connection string) to use an SQL Server database on the local machine (sa and no password!). You can change this in the
DataSetFiller class. The object side of things is similarly cheesy.
Order objects are delivered within serializable collections
OrderCollection. The objects are designed to be real but do not differ in structure from the relational representation at all. You will have to use your imagination!
Probably the best way to go through this is to go work through the test bed code and fall through to the deeper stuff as we go.
Mapping.ModelMapping modelMap =
This first line is creating an instance of the
ModelMapping class which describes the entities (the database entities and objects), the mapping between entities (database column and the object property/field), and also the relationships between the entities. This is a simple collection of objects and is designed to be serializable for a couple of reasons; the first is that it makes it really easy to get an instance from an XML file on disk as we are doing here (I am after all a lazy programmer or should I say efficient - always mix those two up); the second reason we will get too later.
CustomerFactory fact = new CustomerFactory(modelMap);
CustomersCollection customers = fact.Find(new Customer(), "Orders");
This bit is the the funky bit. The
CustomerFactory class is designed to return the
CustomerCollection object based on overloaded
Find methods. The
Find method does all the work of getting the data out of the database, re-modeling it, and returning the object collection. The factory is instantiated with the
ModelMapping instance, and the
Find method is passed a
Customer instance (which would be a search by example overload but it's hard coded) and a string to represent the span of sub items to extract. The span is probably a wee bit cheesy and might be done better, but it is designed to allow a choice as to the depth of fill required (this would allow us to get a simple
CustomerCollection with only the
Customer details filled if required).
Find method itself fills an ADO.NET dataset based on the constructed SQL, then calls the
ModelMapping.Conversion method with the dataset and the type to construct.
CustomersCollection customers =
Now is where it gets cool. The
ModelMapping.Conversion method firstly calls the
RelationalToObject method which loads an XSLT from a compiled resource (RelationalObject.xsl), then applies this transform to the serialized XML representation of the
ModelMapping instance. This results in a data specific XSLT to model the dataset data into the objects data. Sweet!! We use the meta data within the
ModelMapping class to create a specific XSLT. Note that the built in XSLT is pretty funky as it is designed to create another XSLT specifically based on the data structures defined in the
ModelMapping. Oh yeah - creating an XSLT from an XSLT; that was the second reason why the
ModelMapping class is designed to be serializable.
ModelTransforms txs = new ModelTransforms();
XslTransform xsl = txs.RelationalToObject(this);
data.DataSetName = "MappingDataSet";
XmlDocument xml = new XmlDataDocument(data);
XmlReader rdr = xsl.Transform(xml, null);
XmlSerializer xsr = new XmlSerializer(objectType);
Conversion method has created the relational to object XSLT, it simply applies the transform to the dataset data (represented as an
XmlDocument), then de-serializes the resultant data and returns the object. The other side of the function applies a cast to
CustomerCollection, as this function just returns an object. Note the cheesy hard coding of the dataset name; this is fixed in the XSLT, I couldn't think of a way of inferring the name into the XSLT.
The rest of the main code just lists the customers, proving that we have related the orders to them. Then it's the route back. Pretty similar really. Again, it's an XSLT from an XSLT (different one though), and a transform to take the serialized object back into an ADO.NET dataset. The extra bit of code just proves that you can change the data in the object, and have this change back in the dataset. Pretty obvious really!
To wrap up...
Simply put, we use a pre-defined XSLT to transform the
ModelMapping serialized XML data and create a structure specific XSLT to re-model the relational data to objects and vice versa.
This code is pretty cool - even though I do say so myself, it demonstrates many XML techniques with C#, including serialization, the use of XSLT, and the creation of XSLT from XSLT. Perhaps more importantly, it demonstrates how the .NET supports for XML can give another potential solution to a common real world OO application problem.
and the problems...
We decided not to put this idea into production (another reason why it is still a console app). The main reason is the fact that when you think about it, the solution does not lend itself to easy debugging. There are two big steps in the process that are hidden. Firstly, the transform between the representations, and secondly, the serialization to create the objects. We could see ourselves spending long hours tracking down problems in the mapping, spending time looking at the generated XSLT, or trying to fathom why a property was not being de-serialized or a column being lost in the dataset. Probably a little bit wet, but we bottled it, and decided to be a bit more long hand. At the end of the day, the code is probably just a little too concise for its own good.
Another issue to mention with this code is performance. If you remove the hard coded 'A%'
where clause restriction from the SQL and leave an open query to return all rows, you will find that it takes ages! The XSLT is pretty slow - and I'm sure with a bit of work could be made better, the other factor is the de-serialization (which we are also told will be quicker in .NET V2).