Effective Xml Part 1: Choose the right API
This is the first part of a mini-series of blog posts about using Xml on .NET Framework platform in an effective way. Although I will be focusing on .NET Framework platform I hope that at least some of the information will be general enough to apply to working with Xml on any platform.
Managed Xml API contains a few different APIs that allow to start working with Xml documents. These are:
- XDocument/XElement (LINQ to Xml)
The reason why there are that many APIs is that each of these can be very effective when used in some scenarios while cannot be used or is less effective in other scenarios. Let’s take a look at these APIs and scenarios where they shine.
XmlReader – as per MSDN “provides fast, non-cached, forward-only access to XML data”. In general XmlReader reads data from the source stream, ensures that the data is a valid Xml and reports what it read. XmlReader is typically controlled by some code that tells the reader what to read (ReadXXX() methods) or what to skip (MoveToXXX()/Skip() methods). This is the responsibility of the controlling code to process (or cache) the results reported by the XmlReader since the XmlReader will “forget” what it read once moved to the next node.
Note: XmlReader is the lowest level API in the whole .NET Framework that allows reading Xml documents. This means that *any* class in the whole .NET Framework that needs to read Xml documents is – either explicitly or implicitly – using XmlReader under the cover.
If you decide to use XmlReader remember to create the reader using XmlReader.Create() factory method which will create the right reader according to the provided XmlReaderSettings. Avoid using XmlTextReader. It contains quite a few bugs that could not be fixed without breaking existing applications already using it.
- Processing huge files – since XmlReader does not cache any data so it can be used for processing files that would not fit into memory.
- Xsd schema validation – if you just need to validate an Xml document against schema without any further processing using XmlReader is the most effective way.
- You need to cache the data using your own object model - e.g. you read an Atom feed and rather with dealing with Xml elements and attributes you need to deal with Author, Contributor and Link instances your application uses.
- You don’t care about the whole document – e.g. for some reason you just need to check what namespace the root Xml element is in. Why paying the price of loading the whole document just to check this one thing? You can just start reading the document with XmlReader, move to the root element, check the namespace and dispose the XmlReader.
- Simple transformations – along with XmlWriter you can use XmlReader to do simple transformations (like filtering) where you read an Xml documents with XmlReader and use XmlWriter to write the output Xml document.
Scenarios where using XmlReader is not effective or impossible:
- Caching - you need to random access to the data – the data could but did not have to be read yet.
- Complex processing – using XmlReader API is not very easy and you may get to the point when you trade the performance gains off for readability and maintainability of the code.
XPathDocument – is an Xml cache optimized for querying Xml documents. It’s a cache so the whole Xml document has to be read (yes, it is using the XmlReader to do this) before it can be queried. For better query performance and smaller memory footprint the XPathDocument does not allow for any modifications to the cached document.
- You need to query an Xml document with XPath – note that if you have a relatively simple XPath expression(s) you may be able to achieve the same results with XmlReader.
- You need to transform an Xml document with Xslt – Xslt transformations don’t need write access to the source document but usually query the source document a lot. XPathDocument is the best choice for Xslt processing.
Scenarios where using XPathDocument is not effective or impossible:
- You need to be able to modify the loaded Xml document.
XmlDocument – is an API modeled on Xml DOM (Document Object Model). Similarly to the XPathDocument the XmlDocument is a cache, so when working with Xml data from an external source the whole Xml document has to be read first. Unlike the XPathDocument however the cached document can be modified. With XmlDocument it is also possible to build Xml documents programmatically from scratch rather than load them from external sources. The price to pay for this flexibility is worse query performance and bigger memory footprint when comparing to XPathDocument.
- You need to build your Xml document programmatically and do additional processing – note that if you just need to persist the document you have built (so, there is no further processing like querying or transformations after the document has been built) using just XmlWriter will be much more effective.
- You need to load and modify an Xml document and do additional processing – again if there is no additional processing you usually will be better off (performance-wise) if you use XmlReader/XmlWriter tandem.
Scenarios where using XmlDocument is not effective:
- You use Xml document just to do XPath queries or Xslt transformations – use XPathDocument instead.
- You use the XmlDocument API just to create the Xml document and save it – use XmlWriter instead.
- You use the XmlDocument API just to modify the loaded Xml document and save it – for simple cases consider using XmlWriter and XmlWriter to achieve the same goal
XDocument/XElement et al. (LINQ to Xml) – a new XML API added to the .NET Framework in version 3.5 along with a new way of querying – Language Integrated Query (LINQ). With Linq to Xml APIs you can achieve most of what you can achieve with XmlDocument (e.g. build and modify Xml documents, validate Xml documents against Xsd, do Xslt transformations, query with XPath etc.) but in a much easier and lighter way. You have also a new way of querying Xml documents – LINQ queries. In new projects you should prefer LINQ to Xml APIs over XmlDocument.
Although XDocument needs to cache the entire Xml most LINQ queries work with just XElments (or XAttributes etc.) or to be more precise with IEnumerable<T> where T is XElement (or XAttribute etc.). Due to the nature of IEnumerable (deferred execution) it is possible to combine XmlReader and IEnumerable in and be able to query Xml documents without having to load the whole document to memory first. See this blog entry for more details: http://blogs.msdn.com/b/xmlteam/archive/2007/03/24/streaming-with-linq-to-xml-part-2.aspx
- You need to build your document programmatically and do additional processing.
- You need to load and modify an Xml document and do additional processing.
- You want to be able to query an Xml document with LINQ.
- You need to do simple ad hoc transformations – while you can use Xslt to transform Xml documents you can also do transformations using LINQ to Xml APIs which is much lighter than Xslt.
Scenarios where using XmlDocument is not effective:
- You use the XDocument/XElement API just to create the Xml document and save it – use XmlWriter instead.
- You use the XDocument/XElement API just to modify the loaded Xml document and save it – for simple cases consider using XmlWriter and XmlWriter to achieve the same goal.
- You need to do a lot of XPath queries on the document – querying XDocuments with XPath is slower than querying XDocuments with LINQ. Consider replacing XPath queries with LINQ counterparts. If you don’t modify the document at all – you may consider using XPathDocument (however see the section about mixing different APIs below).
One scenario where you may want to use XPath with XDocument however is when you generate queries dynamically. It’s not easy to generate Linq queries on the fly and using XPath in this scenario is probably the best solution if your application is using XDocument/XElement.
Mixing LINQ to Xml with other Xml APIs
There is no magic cast that allows converting XDocument/XElement to XPathDocument or XmlDocument. “Converting” actually means building the whole XmlDocument structure from scratch based on the source XDocument/XElement. It is a costly operation and doing this continuously or for bigger documents can cause performance problems. As far as perf is considered it is usually better to stick to XmlDocument and/or XPathDocument in legacy applications already using these APIs while use LINQ to Xml in new apps.
Writing Xml Documents
The XmlWriter is the lowest level Xml API used to write Xml documents. Any other .NET Framework API persisting Xml documents is using this API. If you just need to write an Xml document this is the fastest way to do it. If you need to write a document that has already been cached (i.e. you already have XmlDocument or XDocument instance in hand) you don’t need to do anything special.