Kirk Marple

Musings of a Yukon and Whidbey pioneer... and feeling the arrows in my back...

<May 2008>
SuMoTuWeThFrSa
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567


Navigation

Subscriptions

Post Categories



Interesting XmlSerializer issue with "circular reference"

I've been banging my head against this issue for the last day, and I finally narrowed it down to a simple test case.

(BTW, I'm using the Nov CTP of Whidbey and .NET 2.0 for this code.)

While sending SOAP messages between my Web services, I'd been using XML serialization to pass CLR objects (DTOs). When trying to send a specific type of object, the serialization barfed with this exception:

System.InvalidOperationException: There was an error generating the XML document
. ---> System.InvalidOperationException: A circular reference was detected while serializing an object of type TestXmlSerializer.MyObject.

I could verify that there definitely wasn't a simple circular reference of object A referencing object B, and vice versa. However, it did have something to do with object A owning a collection to (or keeping a reference to) object B. Object B didn't reference object A, though.

I first thought it was an issue with object A and B having common base classes, but that wasn't it.

Finally, i narrowed it down to an issue with the base class supporting an object ID of type System.Guid. I'd overridden Equals(object o) to state that objects were equal when their object IDs were equal.

Unfortunately, but understandably, it looks like the XML serializer uses Object.Equals to find circular references in any object graph. So, if there were initialized objects that had the empty GUID as the object ID, it would consider them the same object and throw this exception.

I still have to review my code to see the effect of removing the overridden Equals operator. I'm keeping all object instances in generic List<T> collections. I probably just need to implement a different method to test object equivalence, if i need it in my methods.

See the code sample below... the first object instance 'dd' throws the exception, but if i assign a valid GUID to each object, the second instance of the object 'dd' serializes properly.


Code Sample

#region Using directives

using System;
using System.IO;
using System.Collections.Generic;

using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Globalization;

#endregion

namespace TestXmlSerializer
{
	public abstract class Base
	{
		private Guid _objectId = Guid.Empty;
		public Guid ObjectId
		{
			get

			{
				return _objectId;
			}

			set
			{
				_objectId = value;
			}
		}

		public Base()
		{
		}

		public override bool Equals(object obj)
		{
			Base ro = obj as Base;

			if (ro != null)
				return (ro.ObjectId == _objectId);
			else

				return false;
		}

		public override int GetHashCode()
		{
			return _objectId.GetHashCode();
		}
	}

	public class MyObject : Base
	{
		private string _name = null;
		public string Name
		{
			get

			{
				return _name;
			}

			set
			{
				_name = value;
			}
		}

		public MyObject()
		{
		}
	}

	public class D : Base
	{
		public List<Base> Objects = new List<Base>();

		private string _name = null;
		public string Name
		{
			get

			{
				return _name;
			}
			set
			{
				_name = value;
			}
		}

		public D()
		{
		}
	}
	
	class Program
	{
		public static string ToXmlString(object o, Type[] extraTypes)
		{
			if (o == null)
				return null;

			StringBuilder sb = new StringBuilder();

			try

			{
				XmlSerializer x = null;
				StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture);

				XmlWriterSettings xmlSettings = new XmlWriterSettings();
				xmlSettings.OmitXmlDeclaration = true;
				xmlSettings.ConformanceLevel = ConformanceLevel.Document;
				xmlSettings.Indent = true;

				XmlWriter xmlWriter = XmlTextWriter.Create(sw, xmlSettings);

				if (extraTypes != null)
					x = new XmlSerializer(o.GetType(), extraTypes);
				else

					x = new XmlSerializer(o.GetType());

				x.Serialize(xmlWriter, o);

				xmlWriter.Flush();
				xmlWriter.Close();

				sw.Flush();
				sw.Close();
			}
			catch (Exception e)
			{
				Console.WriteLine("Failed serializing to XML.");
				Console.WriteLine(e.ToString());
			}

			return sb.ToString();
		}

		static void Main(string[] args)
		{
			D dd = new D();
			dd.Name = "foo";

			MyObject myo = new MyObject();
			myo.Name = "baz";

			dd.Objects.Add(myo);

			Console.WriteLine("Serializing D....");
			Console.WriteLine(ToXmlString(dd, new Type[] { typeof(MyObject), typeof(Base) }));
			Console.WriteLine("... done.");
			Console.WriteLine();

			dd = new D();
			dd.Name = "foo";
			dd.ObjectId = Guid.NewGuid();

			myo = new MyObject();
			myo.Name = "baz";
			myo.ObjectId = Guid.NewGuid();

			dd.Objects.Add(myo);

			Console.WriteLine("Serializing D (unique)....");
			Console.WriteLine(ToXmlString(dd, new Type[] { typeof(MyObject), typeof(Base) }));
			Console.WriteLine("... done.");
			Console.WriteLine();

			Console.WriteLine("Press <enter> to continue.");
			Console.ReadLine();
		}
	}
}

posted on Tuesday, January 11, 2005 1:24 PM by kmarple


# re:Interesting XmlSerializer issue with @ Tuesday, April 12, 2005 9:32 PM

^_^,Pretty Good!

kmarple

# re:Interesting XmlSerializer issue with @ Friday, April 15, 2005 10:15 AM

^_^,Pretty Good!

kmarple

# re:Interesting XmlSerializer issue with @ Monday, May 16, 2005 10:32 AM

^_~,pretty good!csharpsseeoo

kmarple

# XmlSerializer will use the Equals() method to find circular references @ Thursday, November 09, 2006 8:48 AM

This post saved me quite a bit of time: http://sqljunkies.com/WebLog/kmarple/archive/2005/01/11/6180.aspx

Anonymous




Powered by Dot Net Junkies, by Telligent Systems