These days there seems to be an infinte number of ways to take data returned from the database and map them into your object model.
This article aims to simple show you how to generically map the sql select result set into a list of object instances in your code.
First things first, the code illustrated here requires Sql Server 2005, and .Net 2.0+ framework.
Sql Server 2005 Queries
It doesn’t matter whether you write you queries in code or by using Sql Server SProcs, simple by adding – ‘FOR XML Path(‘ElementName’) – the following to the end of you Select statement, Sql Server will return your result sets in Xml.
Syntax:
SELECT * FROM [TableName] WITH (nolock) FOR XML PATH('TableName')
Example:
SELECT * FROM [Channel] WITH (nolock) FOR XML PATH('Channel')
Results:
<Channel>
<Id>1</Id>
<Type>web</Type>
<Description>standard internet</Description>
</Channel>
<Channel>
<Id>3</Id>
<Type>mobile</Type>
<Description>mobile</Description>
</Channel>
The parameter passed into the Path() statement determines the top level element name – allowing you to shape the xml.
The Entity POCOs
Using the .Net built in xml to class deserialization process, the returned xml will map the top level element to a class name and each sub-element to a public property/member on the object class.
Let’s create the Plain Old C# Object to enable deserialization.
/// Represents a media channel (e.g. web, mobile, iphone, etc.)
public class Channel {
private int _id;
private string _type, _description;
public Channel(){}
public Channel(string type, string description) {
_type = type;
_description = description;
}
/// Get/Set the channel identifier
public int Id {
get { return _id; }
set { _id = value; }
}
/// Get/Set the channel type (e.g. web, mobile, iphone, etc.)
public string Type {
get { return _type; }
set { _type = value; }
}
/// Get/Set the description of the channel.
public string Description {
get { return _description; }
set { _description = value; }
}
}
NOTE: If there are differences in your database schema naming conventions to how you name things in your classes you will need to shape the xml somewhere to make the deserialization process work.
You have two options: you can decorate your class with appropriate Xml Serialization Attributes OR you can change your Sql Query.
Example: the Channel table has a column name desc but you need to map to the public property name Description.
Scenario 1: Shape the xml using the [XmlAttribute]
[XmlAttribute("desc")]
public string Description {
get { return _description; }
set { _description = value; }
}
OR
Scenario 2: Shape the xml in your Sql Query
SELECT [Id], [Type], [Desc] as Description
FROM [Channel]
WITH (nolock)
FOR XML PATH('Channel')
Data Access Code
Ok. You know how to get the data back from the DB in Xml. Now it’s time to read the results into a list of the entity POCOs.
private static readonly IDictionary xmlSerializers = new Dictionary();
/// Generic helper method for reading sql server table rows into entities.
internal IList GetEntities(DbCommand dbCommand) {
IList entities = new List();
using (XmlReader reader = ((SqlDatabase) Database).ExecuteXmlReader(dbCommand)) {
while (!reader.EOF) {
if (reader.IsStartElement()) {
entities.Add(Deserialize(reader.ReadOuterXml()));
}
}
}
return entities;
}
/// Deserialize an xml fragment into the specified Type.
public T Deserialize(string xml) {
using (StringReader sr = new StringReader(xml)) {
if (!xmlSerializers.ContainsKey(typeof (T))){
xmlSerializers.Add(typeof (T), new XmlSerializer(typeof (T), null, new Type[0], null, null));
}
XmlSerializer serializer = xmlSerializers[typeof (T)];
return (T) serializer.Deserialize(sr);
}
}
NOTE: I cache an XmlSerialzer instance for each Type because the .Net framework generates a class dynamically at runtime when the new XmlSerializer() constructor is called. If you have thousands of results coming back from the DB this is timely and memory consuming.
Usage Examples:
/// Get a specific Channel.
public Channel GetChannel(string name) {
IList list = GetEntities(Database.GetStoredProcCommand("usp_GetChannels", 0, name));
return (list != null && list.Count == 1) ? list[0] : null;
}
/// Get a list of all Channels.
public IList GetChannels() {
return GetEntities(Database.GetStoredProcCommand("usp_GetChannels", 0, null));
}
That’s it!
Update: 18-Feb-08 I’ve written a followup article that shows how to do the reverse process – Persisting objects into database using xml serialization
February 15th, 2008 at 8:17 pm #Andrew
Nice writeup, but can you show me an example of how you persist new and updated channels back to the db?
February 16th, 2008 at 11:54 am #Ben
I can. There are many ways, however I get the feeling you’d like to do it using the entity and xml serialization. That isn’t so easy!
Let me think about it!
February 18th, 2008 at 11:56 am #Quickduck
[...] on from the article – Using XmlReader and object Deserialization – which showed how to pull sql query result sets into object instances using the XmlReader and [...]
July 16th, 2008 at 1:23 pm #Adam Short
Just found this page while looking for ways to elegantly deserialize xml back into objects. I just wanted to check, there’s no real reliance on SQL Server here, is there? If I wanted to, I could just as easily just write and read ordinary xml files, right? What I want to do is take a text file in a publicly available format, translate it into xml and use the xml to build objects. Assuming I can handle the first part (which should be relatively trivial), this method should deal with the rest, right?
July 16th, 2008 at 1:27 pm #Gerrod
Adam – yes, you could indeed just read/write to/from an ordinary XML file; it doesn’t have to be going to/from the database. The serialization methods act on an XmlReader/XmlWriter which can be constructed around any stream you like.
The first thing you’ll probably want to do is make your code persist an object so you can see what the resulting XML looks like. This will give you a target to aim for when translating your “public files” into XML.
Hope that helps!
July 16th, 2008 at 5:05 pm #Adam Short
It does indeed, cheers!
February 21st, 2009 at 2:30 am #Matthew
I’ve tried any number of workarounds, and I cannot get the following line to compile:
private static readonly IDictionary xmlSerializers = new Dictionary();
It is insisting that Dictionary() requires two type parameters TKey and TValue and I cannot work out how to make this generic (e.g. Dictionary().
What am I doing wrong?
February 21st, 2009 at 9:47 am #Gerrod
Hi Matthew -
Try using System.Collections.IDictionary instead of System.Collections.Generc.IDictionary.
Hope that helps!
February 21st, 2009 at 1:01 pm #Matthew
Fantastic! All working perfectly.
Thanks for the speedy response!