tags:

views:

39

answers:

2

I have a collection of objects of the same type, let's call it DataItem. The user can view and edit these items in an editor. It should also be possible to compare and merge different items, i.e. some sort of diff/merge for DataItem instances.

The DIFF functionality should compare all (relevant) properties/fields of the items and detect possible differences. The MERGE functionality should then be able to merge two instances by applying selected differences to one of the items.

For example (pseudo objects):

DataItem1 {                  DataItem2 {
    Prop1 = 10                   Prop1 = 10
    Prop2 = 25                   Prop2 = 13
    Prop3 = 0                    Prop3 = 5
    Coll = { 7, 4, 8 }           Coll = { 7, 4, 8, 12 }
}                            }

Now, the user should be provided with a list of differences (i.e. Prop2, Prop3, and Coll) and he should be able to select which differences he wants to eliminate by assigning the value from one item to the other. He should also be able to choose if he wants to assign the value from DataItem1 to DataItem2 or vice versa.

Are there common practices which should be used to implement this functionality?

Since the same editor should also provide undo/redo functionality (using the Command pattern), I was thinking about reusing the ICommand implementations because both scenarios basically handle with property assignments, collection changes, and so on... My idea was to create Difference objects with ICommand properties which can be used to perform a merge operation for this specific Difference.

Btw: The programming language will be C# with .NET 3.5SP1/4.0. However, I think this is more of a language-independent question. Any design pattern/idea/whatsoever is welcome!

+1  A: 

That's pretty much what I do. I have a "Diff" class for an object that uses a PropertyDiff class to compare property values using Reflection. It's too much to paste all the code into SO but this should give you an idea. The collection of not-equal PropertyDiff objects is then displayed to the user who can pick which ones to keep or discard. We use NHibernate so the objects are changed in memory and then all the changes are persisted in a transaction. We did have an older version that built up a collection of SQL commands and that worked as well but you have to be careful that the commands are executed in the same order and in a transaction. The worst thing that can happen is if an exception occurs and both objects are FUBAR.

The PropertyDiff class represents a comparison between the same property on two objects. This works for simple properties only and there's separate code for maintaining collections.

public class PropertyDiff
{
    private bool _isEqual;

    public PropertyDiff(string propertyName, object xvalue, object yvalue)
    {
        PropertyName = propertyName;
        Xvalue = xvalue;
        Yvalue = yvalue;
        _isEqual = Xvalue == Yvalue;
    }

    public string PropertyName { get; private set; }
    public object Xvalue { get; private set; }
    public object Yvalue { get; private set; }
    public bool IsEqual
    {
        get { return _isEqual; }
    }

    internal static IList<PropertyDiff> GetPropertyDiffs(IEnumerable<string> properties, object x, object y)
    {
        if (x.GetType() != y.GetType())
        {
            throw new ArgumentException("Objects must be of same type");
        }

        var list = new List<PropertyDiff>();
        var t = x.GetType();

        foreach (string propertyName in properties)
        {
            PropertyInfo pi = t.GetProperty(propertyName);
            if (pi != null)
            {
                object xVal = pi.GetValue(x, null);
                object yVal = pi.GetValue(y, null);
                PropertyDiff propDiff = new PropertyDiff(propertyName, xVal, yVal);
                list.Add(propDiff);
            }
        }
        return list;
    }
}

And the CompanyDiff class:

    public class CompanyDiff
    {
        private List<string> _propertyNames;
        private IList<PropertyDiff> _propertyDiffs;

        public CompanyDiff(Company companyX, Company companyY)
        {
            this.CompanyX = companyX;
            this.CompanyY = companyY;

            // Build list of property names to be checked
            _propertyNames = new List<string>()
                             {
                                 "Type",
                                 "Name",
                                 "DBA"
                                 // etc.
                             };

            _propertyDiffs = PropertyDiff.GetPropertyDiffs(_propertyNames, this.CompanyX, this.CompanyY);
    }

    public Company CompanyX { get; private set; }
    public Company CompanyY { get; private set; }

    public IList<PropertyDiff> PropertyDiffs
    {
        get { return _propertyDiffs; }
    }
}
Jamie Ide
Oh wow, I did not expect complete source code. :) Thanks! I also thought about using reflection. However, I planned not to use it because of the performance issues involved. Instead, I wanted to implement an `IDiffable` interface in each class with a `Compare(...)` method. What experiences did you make with your reflection-based approach (performance-wise)? I will have to compare collections of around 2000 objects with about 50 properties each (not in one class, but counted recursively).
gehho
I haven't measured the performance because it's been fine. This is somewhat of an exception process (duplicate company entered, etc.) for us so performance is not a primary goal. My advice is to take the easiest approach using reflection and re-factor it if there's a real world performance issue, i.e. don't prematurely optimize.
Jamie Ide
Thanks. Maybe I will also give the reflection-based approach a try first. Then I will see whether it meets my performance requirements.
gehho
A: 

You can use my Pretty Diff tool. The code is available at the site and broken down into its various sections in the documentation. It will accurately diff a piece of code regardless of minification/beautification. It highlights which lines the differences occur on and then highlights which characters are different. Its written all in JavaScript so you can use pieces of it in your automation processes. The actual diff report is HTML only, but you can modify the source code to report out for command line or even XML.

http://mailmarkup.org/prettydiff/prettydiff.html

Thanks for your suggestion. I will look into it and see if I can use some ideas. However, I think it is easier for me to check some C# diff tool.
gehho