Customization and Extensibility tutorial

This tutorial will take you through the basic syntax and results of ZCompare through to the extensibility and customisation features.

Whilst you can read through this and get a good understanding quickly, you may want to download the ZCee Project. This is a very simple C# WPF project which helps see ZCompare in action and the results it gives. Don't worry if you are not familiar with WPF, you only need to modify a simple file - ResultsViewModel.cs

The tutorial uses sample data from the Zaybu.Compare.Data assembly. This is included in the ZCee Project or you can view the simple class definitions here


Step 1 - Compare two simple objects

We are going to compare two products, productA and productB.

using Zaybu.Compare.Data;
Product productA = SampleData.CreateProduct(1);
Product productB = SampleData.CreateProduct(1);
productB.Description = "Product B";
productB.ImageData[1] = 34;
var results = ZCompare.Compare(productA, productB);

SampleData.CreateProduct(1); creates a Product with ID = 1 and populates it with some data.
productA and productB are two separate instances with the same data.
We modify productB by changing the Description and some of it's ImageData.
Pass both objects to the ZCompare.Compare() static method and assign the returned results.

Step 2 - Understanding the compare results

ZCompare tutorial step 1 results

This looks promising, we have an Identical field which is false and a NumberOfDifferences field correctly displaying 2.
The Root is really what is useful though. This is a root tree node and it reflects the object graph tree structure of a Product
Let's expand it and see what's inside.

ZCompare tutorial step 2 root node

Hopefully most of these properties are self explanatory.

ChangedToValue Object reference to productB
ChangedToValueAsString Display friendly representation of productB
Children Child result nodes
IsLeaf Flag indicating if this is the last object in the branch
OriginalValue Object reference to productA
OriginalValueAsString Display friendly representation of productA
Parent Reference to any parent node
PropertyName The name of the property being compared
Status Comparison result status indicating that the object has changed

If we expand the Children node we will see how the results reflect the structure of a Product.

ZCompare tutorial step 2 child nodes

There is a node for each public property of the Product object.
Looking at the Description node we can see how that property has changed.

ZCompare tutorial step 2 child nodes


Step 3 - Query the results

Quite often we find ourselves dealing with large, deep object trees. In these instances traversing the results tree to find the result you want would be tedious. We can probe and query the results for specific objects we are interested in by using the ZCompare.GetResult() method.

As the Product class is a little simple, now is a good time to introduce a more complex object, Supplier. The Supplier has a List<Product> as one of its properties.

In the code below we create 2 Supplier objects. They have seeded sample data populated for them by the CreateSupplier() method.

Supplier supplierA = SampleData.CreateSupplier(1);
Supplier supplierB = SampleData.CreateSupplier(2);
var results = ZCompare.Compare(supplierA, supplierB);
var productResults = results.GetResult(supplierA.Products);

productResults above points to the Products result node.


Another example

var productResult = results.GetResult(supplierA.Products[1]);

productResult contains the results just for the product at location 1 in the list

ZCompare tutorial step 3 query product


And finally, to probe for specific value type properties you can pass in the property name

var productDescriptionResult = results.GetResult(supplierA.Products[0], "Description");

ZCompare tutorial step 3 query product description

Step 4 - Display results

There is a useful method, GetSummary() on every result node

var productResult = results.GetResult(supplierA.Products[1]);
string productSummary = productResult.GetSummary(); 

This gives a simple string output similar to this.

--- Debug Output ---
ListItem Changed
ID Changed - from '2' to '7'
Description Changed - from 'Chocolate Volt Pencil' to 'Emergency Electromagnetic Retrostinker'
Price Changed - from '2.25' to '7.86'
InventoryID Changed - from '22222222-2222-222222222222' to '77777777-7777-777777777777'
Code Changed
	ProductID Changed - from '2' to '7'
	Category Changed - from 'B' to 'G'
ImageData Changed - from '71-93-C6-E3-6F-7C-38-98' to '4B-63-04-E1-68-FF-41-99-EA'

System.ComponentModel.DescriptionAttribute

ZCompare uses the System.ComponentModel.DescriptionAttribute to override the property name and give more readable results.

public class Product
{
        [Description("Product Name")]
        public string Description { get; set; }

Or at runtime of course

ZCompare.SetPropertyDescription(typeof(Product), "Description", typeof(string), "Product Name");

Now the Description property on the Product is overridden with the DescriptionAttribute value 'Product Name'.

...
Product Name Changed - from 'Chocolate Volt Pencil' to 'Emergency Electromagnetic Retrostinker'
...

ZCee Viewer
For full interactive results viewing and validation the ZCee Project is highly recommended. It displays the results in a tree view, highlighting changes and displays summaries. Source code is included so you can easily get your own objects in there for comparison.

ZCompare tutorial step 4 ZCee screenshot



Step 5 - Customisation - Ignore properties, types and namespaces

This step shows us how to specify what gets compared. Large, complex object graphs will potentially produce large, complex comparison results. This obviously has a direct impact on manageability and efficiency. The good news is that ZCompare allows powerful customisation to exclude properties, types and namespaces from the comparison. Let's take another look at the Product type again and it's ImageData property.

...
ImageData Changed - from '71-93-C6-E3-6F-7C-38-98' to '4B-63-04-E1-68-FF-41-99-EA'
...

Let's assume that we are not really interested in whether it has changed or not, we want to ignore it. We have two options.
1) Ignore a single property declaratively

Use the Zaybu.Compare.IgnorePropertyAttribute

public class Product
{
    [IgnoreProperty]
    public byte[] ImageData { get; set; }

Or

2) Inject at runtime

This is really useful for objects you don't have source control over!

ZCompare.IgnoreProperty(typeof(Product), "ImageData", typeof(byte[]));


Similarly we have the ability to ignore types and complete namespaces.

// Exclude a type from being compared            
ZCompare.IgnoreType(typeof(byte[]));
// Exclude an entire namespace from being compared            
ZCompare.IgnoreNamespace("System.Runtime");


Step 6 - Extensibility

This step concentrates on how objects and properties get compared. Again this is very powerful and allows complete control over the comparison operation.

Let's assume that we want to take control over how our Supplier class is compared, and that actually we only need to do a much simpler comparison.

We create our own custom comparitor by implementing...

Zaybu.Compare.Comparitors.ComparitorBase<T>

... and overriding the Compare<T>() method like so...

public class SupplierCustomComparitor : ComparitorBase<Supplier>
{
    public override void Compare(Supplier originalObject, Supplier compareToObject, Results results)
    {
        /* In here we have complete control over what and how things are compared */
        if (originalObject.ID != compareToObject.ID)
        {
            results.AddResult("Supplier ID's are different", ResultStatus.Changed, originalObject.ID, compareToObject.ID);
        }
    }
}

...and registering the custom comparitor with ZCompare.

ZCompare.RegisterComparitor(new SupplierCustomComparitor());
var results = ZCompare.Compare(supplierA, supplierB);

Taking a look at results.Root.GetSummary()

Zaybu.Compare.Data.Supplier Changed
Supplier ID's are different Changed - from '5' to '6'

This demonstrates that we can simplify the comparison operation by implementing a simple rule, if the id's are different then assume the objects are to be different regardless of if the object data is exactly the same.

Note, you can still use ZCompare within the Compare() override

public class SupplierCustomComparitor : ComparitorBase<Supplier>
{
    public override void Compare(Supplier originalObject, Supplier compareToObject, Results results)
    {
        /* In here we have complete control over what and how things are compared */ 
        if (originalObject.ID != compareToObject.ID)
        {
            results.AddResult("Supplier ID's are different", ResultStatus.Changed, originalObject.ID, compareToObject.ID);
        }
        /* Use ZCompare within the override method */ 
        var productResults = ZCompare.Compare(originalObject.Products, compareToObject.Products);
    }
}

Another method to override is the GetStringValue() method. This method determines the display format of the properties being compared. For example, we can create a custom comparitor for the DateTime system type.

public class DateTimeComparitor : ComparitorBase<DateTime>
{
    public override string GetStringValue(DateTime value)
    {
        /* I want my dates displayed in RFC1123 format */
        return String.Format("{0:r}", value);
    }

    public override void Compare(DateTime baseProperty, DateTime compareToProperty, ZCompareResults results)
    {
        /* I want my dates to ignore any time component and just compare on the date value */
        if (baseProperty.ToShortDateString() != compareToProperty.ToShortDateString())
        { 
            results.CurrentResult.Status = ResultStatus.Changed;
        }   
    }
}

Looking at the results below, we can see the format of the Created property is displayed as we specified. Also the Status is NoChange despite the fact that the actual values will differ by a few milliseconds, which we chose to ignore.

var results = ZCompare.Compare(supplierA, supplierB);
var createdResult = results.GetResult(supplierA, "Created";

ZCompare tutorial step 6 custom comparitors

Another simple example to handle string comparisons how you want.

public class StringCustomComparitor : ComparitorBase<string>
{
    public override void Compare(string originalObject, string compareToObject, Results results)
    {
        /* I want to ignore case for my string comparison */ 
        if (!String.Equals(originalObject, compareToObject, StringComparison.OrdinalIgnoreCase)
        {
            results.AddResult("My strings are different!", ResultStatus.Changed, originalObject, compareToObject);
        }
    }
}


Step 7 - Key Properties

When dealing with arrays, collections and lists etc, you may want to compare items in one with items in the other regardless of if they have changed position.

public class Fruit
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Colour { get; set; }
}
List<Fruit> originalFruits = new List<Fruit>();
originalFruits.Add(new Fruit { ID = 1, Name = "Apple", Colour = "Green" });
originalFruits.Add(new Fruit { ID = 2, Name = "Banana", Colour = "Yellow" });
originalFruits.Add(new Fruit { ID = 3, Name = "Cherry", Colour = "Red" });  

Let us assume that we have another list of fruits but in a different order

List<Fruit> newFruits = new List<Fruit>();
newFruits.Add(new Fruit { ID = 3, Name = "Cherry", Colour = "Red" });  
newFruits.Add(new Fruit { ID = 2, Name = "Banana", Colour = "Yellow" });
newFruits.Add(new Fruit { ID = 1, Name = "Apple", Colour = "Green" });

By default, ZCompare will compare the apple in originalFruits with the cherry in newFruits. This is because they are the first object in each of the lists. This may, or not make sense in your application. What you would like though is to have the choice. So for this example we want to compare our apples with another apple and our cherries with other cherries. This is where the KeyPropertyAttribute comes in.

public class Fruit
{    
    public int ID { get; set; }
    [KeyProperty]
    public string Name { get; set; }
    public string Colour { get; set; }
}

This can be declared at compile time as above or injected at runtime below. Again really useful for objects that you don't have source control over!

ZCompare.SetKeyProperty(typeof(Fruit), "Name");

You can also have more than one property to make a compound key. So, in the example below we are only going to compare a red apple with another red apple. If we had a green one then that would be treated as something different.

ZCompare.SetKeyProperties(typeof(Fruit), new List<Fruit> { "Name", "Colour" });
/* And of course declaratively */
public class Fruit
{    
    public int ID { get; set; }
    [KeyProperty]
    public string Name { get; set; }
    [KeyProperty]
    public string Colour { get; set; }
}