Using LINQ to XML Events and Annotations to Track if an XML Tree has Changed

You may optionally be making a number of modifications to a very large XDocument object.  Because of a complicated algorithm, you may not necessarily know ahead of time whether you will be making changes.  If you don’t make any changes, then you don’t want to unnecessarily serialize the tree and save it to disk, or send it over the wire.  This post presents a simple technique to determine if an XDocument has ever been modified since deserialization or creation.

This blog is inactive.
New blog: EricWhite.com/blog

Blog TOCFor example, you might have a very large Open XML document.  The code to modify the document may depend on the state of the document.  The document will be modified by code only if the document contains some arbitrary content.

You could write more complicated code to first look at the XML tree to determine if changes will be made, and then set a flag, and then make the changes.  However, this will increase the size of your code.  Further, this might introduce bugs – if there was a mismatch between the code that determined if a change should be made, and the code to make the changes, then the code may incorrectly flag an XML tree as changed or not.

Instead, we can use a simple technique that will track whether the XML tree has changed.  The technique consists of the following:

  • We add a couple of event handlers to the XDocument.  One (or both) of these event handlers will be called if any changes whatsoever are made to the tree.
  • When the event handler method is called, we remove the event handlers, and add an annotation to the XDocument of type ChangedSemaphore.
  • Then, at any point in time, we can examine the XDocument – if it has a ChangedSemaphore annotation, the XDocument has changed.  If it has no ChangedSemaphore annotation, the XDocument is unmodified.

Here is the code that demonstrates this technique:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

class Program
{
private class ChangedSemaphore { }

private static EventHandler<XObjectChangeEventArgs> ElementChanged =
new EventHandler<XObjectChangeEventArgs>(Program.ElementChangedHandler);

private static void ElementChangedHandler(object sender, XObjectChangeEventArgs e)
{
XObject xSender = (XObject)sender;
XDocument xDocument = xSender.Document;

// Sometimes while moving a node, we may receive an event for a node that has been
// removed from its parent (and therefore its document). This removed node will receive
// an event, and its parent document will be null. In this case, we don't care
// about removing the event handler and adding an annotation. The document will receive
// another event for the parent of the removed node, which will cause the semaphore
// annotation to be added to the parent XDocument.
if (xDocument != null)
{
xDocument.Changing -= ElementChanged;
xDocument.Changed -= ElementChanged;
xDocument.AddAnnotation(new ChangedSemaphore());
}
}

static void Main(string[] args)
{
// load a very large, complicated XDocument for which we want to track change status.
XDocument doc = XDocument.Parse(
@"<?xml version='1.0' encoding='utf-8' ?>
<Root>
<Child>1</Child>
<Child>2</Child>
<Child>3</Child>
</Root>");

// add event handlers to the document so that any changes will be noted
doc.Changing += ElementChanged;
doc.Changed += ElementChanged;

//
//
//
// execute a bunch of code that may or may not change the XDocument
// (in this case, we'll not change the XDocument)
//
//
//

if (doc.Annotation<ChangedSemaphore>() != null)
Console.WriteLine("XDocument has changed");
else
Console.WriteLine("XDocument has not changed");

//
//
//
// execute a bunch of code that may or may not change the XDocument
// (in this case, we'll change the value of the first element)
doc.Root.Element("Child").Value = "5";
//
//
//

if (doc.Annotation<ChangedSemaphore>() != null)
Console.WriteLine("XDocument has changed");
else
Console.WriteLine("XDocument has not changed");
}
}

See LINQ to XML Annotations and LINQ to XML Events for more information about those features of LINQ to XML. 

The code is attached to this post.

Program.cs