This code allows me to compare two complex objects, which are stored in a relational database, to see what has been removed from the objects. These objects have a unique integer primary key. They are also composed of other objects hierarchically.
The Difference
method is used within a database application (code not shown here). When an object is retrieved from the database, it is cloned. At the point of saving the "new" object this is compared with the "old" clone, and Difference
returns a summary of the types and IDs of those objects which have been removed by the user. Thus the database application can:
- Insert new database entities and update existing ones.
- Delete removed records returned by
Difference
to prevent orphaned records in the database.
/// <summary>
/// Data which has a unique integer identifier and is composed of other similar objects
/// </summary>
interface IHierarchicalDatabaseEntity
{
/// <summary>
/// The unique identifier
/// </summary>
int ID { get; }
/// <summary>
/// The other objects that this is composed of
/// </summary>
IEnumerable<IHierarchicalDatabaseEntity> Children { get; }
}
/// <summary>
/// Helper methods for IHierarchicalDatabaseEntity
/// </summary>
public static class HierarchicalDatabaseEntityExtensions
{
/// <summary>
/// Summarises the differences between the old and new database entities.
/// Each entity which is present in the old data but not in the new will result in an element being returned.
/// </summary>
/// <param name="oldData">the old database entity</param>
/// <param name="newData">the new database entity</param>
/// <returns>an enumeration over the types and IDs of data which is present in the old data but not in the new</returns>
public static IEnumerable<KeyValuePair<Type, List<int>>> Difference(IHierarchicalDatabaseEntity oldData, IHierarchicalDatabaseEntity newData)
{
ListLookup<Type, int> result = oldData.CollateIDs();
result.RemoveAll(newData.CollateIDs());
return result;
}
/// <summary>
/// Gets a summary of the data within some database entities
/// </summary>
/// <param name="databaseEntities">the objects to be summarised</param>
/// <returns>a ListLookup where the keys are the types of the objects and the values are the unique IDs</returns>
public static ListLookup<Type, int> CollateIDs(IEnumerable<IHierarchicalDatabaseEntity> databaseEntities)
{
ListLookup<Type, int> listLookup = new ListLookup<Type, int>();
foreach (IHierarchicalDatabaseEntity entity in databaseEntities)
{
listLookup.Add(entity.GetType(), entity.ID);
}
return listLookup;
}
/// <summary>
/// Gets a summary of a database entity and all its children
/// </summary>
/// <param name="parent">the parent object</param>
/// <returns>a ListLookup where the keys are the types of the objects and the values are the unique IDs</returns>
public static ListLookup<Type, int> CollateIDs(this IHierarchicalDatabaseEntity parent)
{
return CollateIDs(parent.GetAllChildren());
}
/// <summary>
/// Gets all the children of a IHierarchicalDatabaseEntity
/// </summary>
/// <param name="parent">the parent object</param>
/// <returns>the parent and all its children</returns>
public static IEnumerable<IHierarchicalDatabaseEntity> GetAllChildren(this IHierarchicalDatabaseEntity parent)
{
Queue<IHierarchicalDatabaseEntity> children = new Queue<IHierarchicalDatabaseEntity>();
children.Enqueue(parent);
while (children.Any())
{
IHierarchicalDatabaseEntity first = children.Dequeue();
yield return first;
foreach (IHierarchicalDatabaseEntity child in first.Children)
{
children.Enqueue(child);
}
}
}
}
/// <summary>
/// An association between key objects and multiple value objects
/// </summary>
/// <typeparam name="TK">the keys</typeparam>
/// <typeparam name="TV">the values</typeparam>
public class ListLookup<TK, TV>: IEnumerable<KeyValuePair<TK, List<TV>>>
{
private readonly Dictionary<TK, List<TV>> dictionary = new Dictionary<TK, List<TV>>();
/// <summary>
/// Add an association
/// </summary>
/// <param name="key">the key</param>
/// <param name="value">the value</param>
public void Add(TK key, TV value)
{
List<TV> values;
if (!dictionary.TryGetValue(key, out values))
{
values = new List<TV>();
dictionary[key] = values;
}
}
/// <summary>
/// Removes all the values from this object which are present in the other object
/// </summary>
/// <param name="other"></param>
public void RemoveAll(ListLookup<TK, TV> other)
{
foreach (KeyValuePair<TK, List<TV>> kvp in dictionary)
{
List<TV> otherList;
if(other.dictionary.TryGetValue(kvp.Key, out otherList))
{
kvp.Value.RemoveAll(v => otherList.Contains(v));
}
}
}
public IEnumerator<KeyValuePair<TK, List<TV>>> GetEnumerator()
{
return dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}