Following on from a recent post regarding XML serialisation of ADO.NET Entity Framework one issue I've come across is getting entities within the XML in the correct order.
The main issue is that the EntityCollection<IEntityWithRelationships> type used to maintain references within the entity framework is that its un-ordered. This makes perfect sense of course as entities in a database have no reliable order.
Even though this makes sense its very annoying if you just want your entities to come out in a defined order within an object structure and the any XML serialised from it. You could of course order the XML once it has been serialised, either via a transform, or in code, but having data contract references makes this pretty difficult.
I've come up with a pretty hacky mechanism that forces the order that objects are returned via the EntityCollection Enumerators. The solution uses reflection and won't be guaranteed to work in future versions of the framework. Essentially it finds the HashSet object used by EntityCollection to store its values and re-orders the data structure that the HashSet loops through in its enumerator.
Once a sort order has been applied to the entity collection the only code that runs against the collection must only work via the hashsets enumerator. I.e. use a context object solely to get all the objects for the re-order process and only ever read from the collections that have been re-ordered after that. Any attempt to add or remove items will cause the HashSet object to blow up as its underlying data structures have been messed with.
The code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Collections;
using System.Reflection;
namespace Examples
{
public static class EntityUtil
{
/// <summary>
/// Reorders the entities in an entity collection by the keySelector function.
/// </summary>
/// <remarks>
/// This class uses reflection to re-write the slots in the underlying hashset used by the entity
/// collection to contain the references.
///
/// The only action that should be taken on the entity collection after this method has been called
/// is via its enumerators, as the hash set is not left in a consistant state.
/// </remarks>
/// <typeparam name="TSource">The type of the entity objects.</typeparam>
/// <typeparam name="TKey">The type of the key to order by.</typeparam>
/// <param name="source">The entity collection source to re-order.</param>
/// <param name="keySelector">The key selector function that returns the key to order by.</param>
public static void ReorderEntities<TSource, TKey>(
this EntityCollection<TSource> source, Func<TSource, TKey> keySelector)
where TSource : class, IEntityWithRelationships
{
HashSet<TSource> relatedEntities = (HashSet<TSource>)
typeof(EntityCollection<TSource>).GetProperty("RelatedEntities", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source, null);
// re-order the sets slots to fix the ordering, (but mess up everything else probably)
Type slotType = typeof(HashSet<TSource>).Assembly.GetType("System.Collections.Generic.HashSet`1+Slot").MakeGenericType(new Type[] { typeof(TSource) });
FieldInfo slotField = typeof(HashSet<TSource>).GetField("m_slots", BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo lastIndex = typeof(HashSet<TSource>).GetField("m_lastIndex", BindingFlags.Instance | BindingFlags.NonPublic);
System.Array slots = Array.CreateInstance(slotType, relatedEntities.Count);
int index = 0;
foreach (TSource obj in new List<TSource>(relatedEntities).OrderBy(keySelector))
{
object test = Activator.CreateInstance(slotType);
slotType.GetField("hashCode", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(test, obj.GetHashCode());
slotType.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(test, obj);
slotType.GetField("next", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(test, -1);
slots.SetValue(test, index++);
}
slotField.SetValue(relatedEntities, slots);
lastIndex.SetValue(relatedEntities, slots.Length);
}
}
}
The reflection code itself is quite interesting as it find and constructs an inner type (Slot) of a generic type. The inner type is a struct so the Activator class is used as struct's don't have a constructor to invoke.
An example use of the code is:
Customer customer;
using (ContextContainer context = new ContextContainer())
{
// cause a load of static data types first
customer = context.Customers
.Include("Addresses")
.Include("Addresses.AddressLines")
.First(e => e.CustomerId == customerId);
}
// reorder the structure in memory
customer.Addresses.ReorderEntities(e => e.Order);
foreach (Address a in customer.Addresses)
{
a.AddressLines.ReorderEntities(e => e.Order);
}
The structure will now have the correct serialisation order. FYI an example xslt snippet that works correctly with ordered referenced entites is:
<xsl:for-each select="do:Addresses/do:Address">
<xsl:apply-templates select="self::node()[@z:Id]"/>
<xsl:for-each select="self::node()[@z:Ref]">
<xsl:variable name ="ref" select="@z:Ref"/>
<xsl:apply-templates select="//do:Address[@z:Id = $ref]"/>
</xsl:for-each>
</xsl:for-each>