tags:

views:

76

answers:

3

I have a form where users can modify a collection of objects using a DataGrid. When the form is opened I create a deep copy of the original collection and if the Cancel button is pressed I just discard that copy.

The problem is that when the OK button is pressed I have to reconcile the changes which might be:

  • Modified properties of existing objects
  • New objects added to any place in the collection.
  • Existing objects removed.
  • Existing objects re-ordered.

Since I need to keep the original references I can't just clear the collection and add the modified items.

Do you know a simple algorithm that would synchronize two collections like this?

I'm using C# 3.5 so LINQ is available.

+1  A: 

Why not keep a copy of the original, and bind to the actual collection. If you bind to the actual collection, then there is no reconciliation. When cancel is pressed, simply replace the actual collection with the copy of the original. It is basically an inversion of what you are trying to do...but it should be a lot easier to manage.

jrista
That's actually the same problem. At cancel I would have to make the same reconciliation since objects could have been removed or reordered.
Joseph Liberty
At cancel, you want to restore the collection to the state before any edits were made at all. By copying the collection before binding to it, you have that "original" state. You simply clear the bound collection, and fill it with the contents of the copy. Cancellation shouldn't mean that deletions and reordering is keept, while additions and edits are discarded. Cancellation means "restore the collection to its previous state". No reconciliation on cancel should be required.
jrista
But properties of objects will be modified too.
Joseph Liberty
I may not have been clear, but you are copying the list. That should create a complete duplicate of the entire thing, objects included. I think its obvious that if you only shallow-cloned, it wouldn't work. I don't know how else to explain it, but he solution is quite simple, and it should greatly reduce your workload. It WILL consume memory, but unless you are binding to a million-item list, that is unlikely to be a problem.
jrista
If I make a deep copy of the list I loose the original references which should remain the same.
Joseph Liberty
Why should they remain the same? You are trying to make a backup of state. That state is being bound to a view that can modify it. In the event that the Cancel button is clicked, you want to restore that state to its previous state. Why would you want to keep them as references when you are explicitly trying to create a copy of it?
jrista
+1  A: 

The way I've dealt with this depends on the collections of objects having a unique ID. I also pass in the repository to deal with this as well, but for brevity I left it out. It's similiar to the following:

public static void MergeFields(IEnumerable<TYPE1> persistedValues, IEnumerable<TYPE1> newValues)
{
    var leftIds = persistedValues.Select(x => x.Id).ToArray();
    var rightIds = newValues.Select(x => x.Id).ToArray();

    var toUpdate = rightIds.Intersect(leftIds).ToArray();
    var toAdd = rightIds.Except(leftIds).ToArray();
    var toDelete = leftIds.Except(rightIds).ToArray();

    foreach(var id in toUpdate){
        var leftScope = persistedValues.Single(x => x.ID == id);
        var rightScope = newValues.Single(x => x.ID == id);

        // Map appropriate values from rightScope over to leftScope
    }

    foreach(var id in toAdd) {
        var rightScope = newValues.Single(x => x.ID == id);
        // Add rightScope to the repository
    }

    foreach(var id in toDelete) {
        var leftScope = persistedValues.Single(x => x.ID == id);
        // Remove leftScope from the repository
    }

    // persist the repository
}
Agent_9191
A: 

I just worked out the following algorithm for a project I'm working on and I think it complies with your requirements. It follows this steps:

  1. Creates a shallow copy of the original list.
  2. Clears the original list.
  3. Foreach item in the modified list.
    1. If it finds the original object adds it to the list and updates it's properties.
    2. If not just add it to the list.

This method maintains the new order and reuses the original references but requires that your objects have an unique identifier. I'm using a Guid for that.

var originalPersons = m_originalList.ToList();
m_originalList.Clear();
foreach (var modifiedPerson in m_modifiedList)
{
    var originalPerson = originalPersons.FirstOrDefault(c => c.ID == modifiedPerson.ID);
    if (originalPerson == null)
        m_originalList.Add(modifiedPerson);
    else
    {
        m_originalList.Add(originalPerson);
        originalPerson.Document = modifiedPerson.Document;
        originalPerson.Name = modifiedPerson.Name;
        ...
    }
}

Good luck.

Julio César