Wednesday, 28 May 2008

Serialization in SSDS

SQL Server Data Services returns data in POX (plain ol' XML) format.  If you look carefully at the way the data is returned, you can see that individual flex entities look somewhat familiar to what is produced from the XmlSerializer.  I say 'somewhat' because we have the data wrapped in this 'EntitySet' tag.

<s:EntitySet 
   xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:x="http://www.w3.org/2001/XMLSchema">
  <PictureTag>
    <s:Id>1e803f90-e5e5-4524-9e3c-3ba960be9494</s:Id>
    <s:Version>1</s:Version>
    <PictureId xsi:type="x:string">3a1714bc-8771-4f6c-8d16-93238f126d9f</PictureId>
    <TagId xsi:type="x:string">ab696b85-1bdc-4bed-8824-dfbf9b67b5cc</TagId>
  </PictureTag>
</s:EntitySet>

I am using the PictureTag from the PhluffyFotos sample application, but this could be any flexible entity.  If we extract the PictureTag element and children from the surrounding EntitySet, we can very easily deserialize this into a class.

Given a class 'PictureTag':

public class PictureTag
{
    [XmlElement(Namespace="http://schemas.microsoft.com/sitka/2008/03/")]
    public string Id { get; set; }
    [XmlElement(Namespace = "http://schemas.microsoft.com/sitka/2008/03/")]
    public int Version { get; set; }
    public string PictureId { get; set; }
    public string TagId { get; set; }
}

We can deserialize this class in just 3 lines of code:

string xml = @"<s:EntitySet 
               xmlns:s=""http://schemas.microsoft.com/sitka/2008/03/""
               xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
               xmlns:x=""http://www.w3.org/2001/XMLSchema"">
              <PictureTag>
                <s:Id>1e803f90-e5e5-4524-9e3c-3ba960be9494</s:Id>
                <s:Version>1</s:Version>
                <PictureId xsi:type=""x:string"">3a1714bc-8771-4f6c-8d16-93238f126d9f</PictureId>
                <TagId xsi:type=""x:string"">ab696b85-1bdc-4bed-8824-dfbf9b67b5cc</TagId>
              </PictureTag>
            </s:EntitySet>";

var xmlTag = XElement.Parse(xml).Element("PictureTag");

XmlSerializer xs = new XmlSerializer(typeof(PictureTag));
var tag = (PictureTag)xs.Deserialize(xmlTag.CreateReader());

Now, the 'tag' variable is a PictureTag instance.  As you can see, deserialization is a snap.  What about serialization, however?

If I reverse the process using the following code, you will notice that something has changed:

using (var ms = new MemoryStream())
{
    //add a bunch of namespaces and override the default ones too
    XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
    namespaces.Add("s", @"http://schemas.microsoft.com/sitka/2008/03/");
    namespaces.Add("x", @"http://www.w3.org/2001/XMLSchema");
    namespaces.Add("xsi", @"http://www.w3.org/2001/XMLSchema-instance");

    XmlWriterSettings xws = new XmlWriterSettings();
    xws.Indent = true;
    xws.OmitXmlDeclaration = true;

    using (XmlWriter writer = XmlWriter.Create(ms, xws))
    {
        xs.Serialize(writer, tag, namespaces);
        ms.Position = 0; //reset to beginning

        using (var sr = new StreamReader(ms))
        {
            xmlTag = XElement.Parse(sr.ReadToEnd());
        }
    }
}

If I look in the 'xmlTag' XElement, I get somewhat different XML back:

<PictureTag
  xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:x="http://www.w3.org/2001/XMLSchema">
  <s:Id>1e803f90-e5e5-4524-9e3c-3ba960be9494</s:Id>
  <s:Version>1</s:Version>
  <PictureId>3a1714bc-8771-4f6c-8d16-93238f126d9f</PictureId>
  <TagId>ab696b85-1bdc-4bed-8824-dfbf9b67b5cc</TagId>
</PictureTag>

I lost the 'xsi:type' attributes that I need in order to signal to SSDS how to treat the type.  Bummer.

We can manually add the attributes (fix-up) after the serialization.  Let's see how that would work:

XNamespace xsi = @"http://www.w3.org/2001/XMLSchema-instance";
XNamespace ns = @"http://schemas.microsoft.com/sitka/2008/03/";

//xmlTag is XElement holding our
var nodes = xmlTag.Descendants();
foreach (var node in nodes)
{
    if (node.Name != (ns + "Id") && node.Name != (ns + "Version"))
    {
        node.Add(
            new XAttribute(
                xsi + "type",
                GetAttributeType(node.Name.LocalName.ToString(), typeof(PictureTag))
                )
            );
    }
}

We need to loop through each node and set the 'xsi:type' attribute appropriately.  Here is my quick and dirty implementation:

static Dictionary<Type, string> xsdTypes = new Dictionary<Type, string>()
{
    {typeof(string), "x:string"},
    {typeof(int), "x:decimal"},
    {typeof(long), "x:decimal"},
    {typeof(float), "x:decimal"},
    {typeof(decimal), "x:decimal"},
    {typeof(short), "x:decimal"},
    {typeof(DateTime), "x:dateTime"},
    {typeof(bool), "x:boolean"},
    {typeof(byte[]), "x:base64Binary"}
};

private static string GetAttributeType(string name, Type type)
{
    var prop = type.GetProperty(name);

    if (prop != null)
    {
        if (xsdTypes.ContainsKey(prop.PropertyType))
            return xsdTypes[prop.PropertyType];
    }

    return xsdTypes[typeof(string)];
}

When all is said and done, I am back to what I need:

<PictureTag
   xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:x="http://www.w3.org/2001/XMLSchema">
  <s:Id>1e803f90-e5e5-4524-9e3c-3ba960be9494</s:Id>
  <s:Version>1</s:Version>
  <PictureId xsi:type="x:string">3a1714bc-8771-4f6c-8d16-93238f126d9f</PictureId>
  <TagId xsi:type="x:string">ab696b85-1bdc-4bed-8824-dfbf9b67b5cc</TagId>
</PictureTag>

However, I am not sure I really like this technique.  It seems like that if I am going to be using Reflection to 'fix-up' the XML from the XmlSerializer, I might as well just use it to build the entire thing.  With that in mind, here is the next implementation of SSDS Serialization:

public static XElement CreateEntity<T>(T instance, string id) where T : class, new()
{
    XNamespace ns = @"http://schemas.microsoft.com/sitka/2008/03/";
    XNamespace xsi = @"http://www.w3.org/2001/XMLSchema-instance";
    XNamespace x = @"http://www.w3.org/2001/XMLSchema";

    if (instance == null)
        return null;

    if (String.IsNullOrEmpty(id))
        throw new ArgumentNullException("id");

    Type type = typeof(T);

    // Create an element for each non-system, non-binary property on the class
    var properties =
        from p in type.GetProperties()
        where xsdTypes.ContainsKey(p.PropertyType) &&
              p.Name != "Id" &&
              p.Name != "Version" &&
              !p.PropertyType.Equals(typeof(byte[]))
        select new XElement(p.Name,
                   new XAttribute(xsi + "type", xsdTypes[p.PropertyType]),
                   p.GetValue(instance, null)
               );

    // Binary properties are special, since they must be serialized as Base-64
    var binaryProperties =
        from p in type.GetProperties()
        where p.PropertyType.Equals(typeof(byte[])) && (p.GetValue(instance, null) != null)
        select new XElement(p.Name,
                   new XAttribute(xsi + "type", xsdTypes[p.PropertyType]),
                   Convert.ToBase64String((byte[])p.GetValue(instance, null))
               );

    // Construct the Xml
    var xml = new XElement(type.Name,
        new XElement(ns + "Id", id), //here is the Id element
        new XAttribute(XNamespace.Xmlns + "s", ns),
        new XAttribute(XNamespace.Xmlns + "xsi", xsi),
        new XAttribute(XNamespace.Xmlns + "x", x),
        properties,
        binaryProperties
        );

    return xml;
}

In this case, we are using Reflection to build a list of Properties in the object and depending on the type (byte[] array is special), we build the XElement ourselves and assemble the entity by hand.  We can use it like this:

XElement entity = CreateEntity<PictureTag>(tag, tag.Id);

Of course, there are a number of other techniques that I am not covering in this already very long post.  Perhaps in my next post we will look at a few others.