Fluent Transform Extensions

Hi, today I would like to show you how trivial ETL in C# 4.0 can be. Many times we need transform model objects from one kind to another. I try to prepare for today easiest way to do that. I prepared small helper class with extensions for our objects with tree following methods: Serialize, Transform and Deserialize. You may be wonder how it works. Below I show you an example.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace FluentTransformExtensionsSandbox {

  [Serializable]
  public class DataA {
    public string PropertyA { get; set; }
  }

  [Serializable]
  public class DataB {
    public string PropertyB { get; set; }
  }

  class Program {
    static void Main(string[] args) {
      DataA a;
      DataB b;

      a = new DataA { PropertyA = "VAL" };

      b = a.Serialize()
          .Transform(xsltB)
          .Deserialize(typeof(DataB)) as DataB;

      Console.WriteLine("a:");
      Console.WriteLine(a.Serialize());
      Console.WriteLine("b:");
      Console.WriteLine(b.Serialize());

      var meter = Stopwatch.StartNew();
      Parallel.For(0, 2500, i => {
        a = b.Serialize()
            .Transform(xsltA)
            .Deserialize(typeof(DataA)) as DataA;

        b = a.Serialize()
            .Transform(xsltB)
            .Deserialize(typeof(DataB)) as DataB;
      });
      meter.Stop();

      Console.WriteLine();
      Console.WriteLine("5k transforms took {0} ms.", meter.ElapsedMilliseconds);
      Console.WriteLine();
      Console.WriteLine("Press any key to close...");

      Console.ReadKey();
    }

    static string xsltB =
@"<?xml version=""1.0""?>
<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">

<xsl:template match=""DataA"">
<DataB>
  <PropertyB><xsl:value-of select="".""/></PropertyB>
</DataB>
</xsl:template>

<xsl:template match=""/"">
  <xsl:apply-templates/>
</xsl:template>

</xsl:stylesheet>";

    static string xsltA =
@"<?xml version=""1.0""?>
<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">

<xsl:template match=""DataB"">
<DataA>
  <PropertyA><xsl:value-of select="".""/></PropertyA>
</DataA>
</xsl:template>

<xsl:template match=""/"">
  <xsl:apply-templates/>
</xsl:template>

</xsl:stylesheet>";
  }
}

Ok, and an output for this test looks like below.

a:
<?xml version="1.0" encoding="utf-16"?>
<DataA xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <PropertyA>VAL</PropertyA>
</DataA>
b:
<?xml version="1.0" encoding="utf-16"?>
<DataB xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <PropertyB>
  VAL
</PropertyB>
</DataB>

5k transforms took 341 ms.

Press any key to close...

And now it is time to show you implementation of the fluent transform extensions below.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Xsl;

namespace FluentTransformExtensionsSandbox {

  public static class FluentTransformExtensions {

    private static ThreadLocal<Dictionary<Type, XmlSerializer>>
      serializers = new ThreadLocal<Dictionary<Type, XmlSerializer>>
        (() => new Dictionary<Type, XmlSerializer>());

    private static XmlSerializer GetSerializer(Type type) {
      XmlSerializer serializer;

      if (!serializers.Value.TryGetValue(type, out serializer)) {
        serializer = new XmlSerializer(type);
        serializers.Value.Add(type, serializer);
      }

      return serializer;
    }

    public static object Serialize(this object value) {
      if (value == null)
        return value;

      var serializer = GetSerializer(value.GetType());
      var writer = new StringWriter();
      serializer.Serialize(writer, value);

      return writer.ToString();
    }

    private static ThreadLocal<Dictionary<int, XslCompiledTransform>>
      transforms = new ThreadLocal<Dictionary<int, XslCompiledTransform>>
        (() => new Dictionary<int, XslCompiledTransform>());

    private static XslCompiledTransform GetCompiledTransform(string xslt) {
      var hashCode = xslt.GetHashCode();
      XslCompiledTransform transform;

      if (!transforms.Value.TryGetValue(hashCode, out transform)) {
        transform = new XslCompiledTransform();
        var input = new StringReader(xslt);
        var reader = XmlReader.Create(input);
        transform.Load(reader);

        transforms.Value.Add(hashCode, transform);
      }

      return transform;
    }

    public static object Transform(this object xmlValue, string xslt) {
      if (xmlValue == null)
        return xmlValue;

      var transform = GetCompiledTransform(xslt);

      var input = new StringReader(xmlValue.ToString());
      var xmlInput = XmlReader.Create(input);
      var output = new StringWriter();
      var xmlOutput = XmlWriter.Create(output);

      transform.Transform(xmlInput, xmlOutput);

      return output.ToString();
    }

    public static object Deserialize(this object xmlValue, Type type) {
      if (xmlValue == null)
        return xmlValue;

      var serializer = GetSerializer(type);
      var reader = new StringReader(xmlValue.ToString());

      return serializer.Deserialize(reader);
    }
  }
}

I hope you enjoy this example. Of course if you wish you can create examples of XML to HTML transformations or XML to CSV transformations or CSV to XML to Object or anything you want.

P ;).

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.