Darren Jefford posted last month an entry called "Calling a .NET Assembly from Orchestration with Schema Types" on his weblog, where he outlined how to call .NET components that manipulated messages from BizTalk Server orchestrations. It's a really nice entry, well worth reading.
One particular thing that Darren said, and that I had already noticed, was that you could convert a BizTalk message instance into a .NET strongly typed class using XLANsGPart.RetrieveAs(). Darren's entry got me thinking, though, as to whether this was efficiently implemented.
Let's start with a little basics: BizTalk represents a message in an orchestration as an instance of the XLANGMessage class (well, actually a subclass of it, since XLANGMessage is abstract). Each message is built from a set of message parts (in other words, XLANGMessage possibly represents a multipart message type); you can get at each part by using the indexer, which returns instances of types derived from the abstract XLANGPart class. XLANGPart is the one that defines the useful RetrieveAs() method.
Now, on an orchestration, when using a component that handles instances of XLANGPart, you'll likely get an instace of the concrete Microsoft.XLANGs.Core.XSDPart class, which itself inherits from an abstract class called Microsoft.XLANGs.Core.Part class.
It is this Part class that actually contains the implementation of the RetrieveAs() method, by simply forwarding it to an instance of a ValueToken class, which itself simply forwards it to Value.RetrieveAs(). The latter is a simple method, defined as:
public object RetrieveAs(Type t)
if ((t == typeof(XmlDocument)) || (t == typeof(XmlNode)))
if (t == typeof(XmlReader))
if (t == typeof(Stream))
string text1 = null;
return this.GetStream(ref text1, false);
The part that interests us is the last call, which calls Value.GetObject(). Now, from here on, things go quite a bit around till we get to the part where we are interested, which is where the Message content is deserialized into a .NET class. This gets done inside the ObjectFromStreamReader() in the Microsoft.XLANGs.RuntimeTypes.XmlHelpers class, which is pretty simple:
public static object ObjectFromStreamReader(StreamReader sr, Type t)
XmlSerializer serializer1 = new XmlSerializer(t);
object obj1 = serializer1.Deserialize(sr);
return XmlHelpers._check(obj1, t);
Quite what one would expect, right? Well, the problem is, this is highly inefficient. As many of you by now noticed, this code is not optimal because it doesn't reuse XmlSerializer instances for a given type. So each time the conversion needs to be made, a new instance of the XmlSerializer gets created, which in turns goes ahead and generates new code for the deserialization, compiles it, and loads the generated assembly into memory.
This is bad, not only because it is a very expensive operation, but also because it leaks memory (since the generated assemblies cannot be unloaded from memory until the entire AppDomain is unloaded).
What BizTalk should be doing is caching the XmlSerializer instances it creates for a given time, like, for example, the ASP.NET framework does for webservices or how WSE does (look at the XmlSerializerCache implementation).
Until that is done, creating XML serializable classes in BizTalk trhough XLANGPart.RetrieveAs() is a very expensive proposition. Well, to be fair, they do implement one particular optimization, and that is that if the object has been created for a given message instance, it can be reused without having to go through the serializer again, but that's a fairly small optimization compared to the underlying problem.