Reconciling a new BindingList into a master BindingList using LINQ
- by Neo
I have a seemingly simple problem whereby I wish to reconcile two lists so that an 'old' master list is updated by a 'new' list containing updated elements. Elements are denoted by a key property. These are my requirements:
All elements in either list that have the same key results in an assignment of that element from the 'new' list over the original element in the 'old' list only if any properties have changed.
Any elements in the 'new' list that have keys not in the 'old' list will be added to the 'old' list.
Any elements in the 'old' list that have keys not in the 'new' list will be removed from the 'old' list.
I found an equivalent problem here - http://stackoverflow.com/questions/161432/ - but it hasn't really been answered properly. So, I came up with an algorithm to iterate through the old and new lists and perform the reconciliation as per the above. Before anyone asks why I'm not just replacing the old list object with the new list object in its entirety, it's for presentation purposes - this is a BindingList bound to a grid on a GUI and I need to prevent refresh artifacts such as blinking, scrollbars moving, etc. So the list object must remain the same, only its updated elements changed.
Another thing to note is that the objects in the 'new' list, even if the key is the same and all the properties are the same, are completely different instances to the equivalent objects in the 'old' list, so copying references is not an option.
Below is what I've come up with so far - it's a generic extension method for a BindingList. I've put comments in to demonstrate what I'm trying to do.
public static class BindingListExtension
{
public static void Reconcile<T>(this BindingList<T> left,
BindingList<T> right,
string key)
{
PropertyInfo piKey = typeof(T).GetProperty(key);
// Go through each item in the new list in order to find all updated and new elements
foreach (T newObj in right)
{
// First, find an object in the new list that shares its key with an object in the old list
T oldObj = left.First(call => piKey.GetValue(call, null).Equals(piKey.GetValue(newObj, null)));
if (oldObj != null)
{
// An object in each list was found with the same key, so now check to see if any properties have changed and
// if any have, then assign the object from the new list over the top of the equivalent element in the old list
foreach (PropertyInfo pi in typeof(T).GetProperties())
{
if (!pi.GetValue(oldObj, null).Equals(pi.GetValue(newObj, null)))
{
left[left.IndexOf(oldObj)] = newObj;
break;
}
}
}
else
{
// The object in the new list is brand new (has a new key), so add it to the old list
left.Add(newObj);
}
}
// Now, go through each item in the old list to find all elements with keys no longer in the new list
foreach (T oldObj in left)
{
// Look for an element in the new list with a key matching an element in the old list
if (right.First(call => piKey.GetValue(call, null).Equals(piKey.GetValue(oldObj, null))) == null)
{
// A matching element cannot be found in the new list, so remove the item from the old list
left.Remove(oldObj);
}
}
}
}
It can be called like this:
_oldBindingList.Reconcile(newBindingList, "MyKey")
However, I'm looking for perhaps a method of doing the same using LINQ type methods such as GroupJoin<, Join<, Select<, SelectMany<, Intersect<, etc. So far, the problem I've had is that each of these LINQ type methods result in brand new intermediary lists (as a return value) and really, I only want to modify the existing list for all the above reasons.
If anyone can help with this, would be most appreciated. If not, no worries, the above method (as it were) will suffice for now.
Thanks,
Jason