Quick Start Tutorial

The Scenario

Let's introduce a typical scenario of where ZCompare is useful.

I have my original data from the database, my user has made many changes to this data which reflects down through my object tree. I want to know if they have made changes to data, created new data or deleted data. More importantly I want to know what exactly has changed and what it has changed from. Sound familiar?

Let's assume we have a large .net object, a list of Suppliers for example. See here for class definitions. Lets assume that originalSuppliers are a list of the current suppliers from the database. We are creating them here on the fly for simplicity using a helper class 'SampleData'.

'SampleData' can be found in the from the Zaybu.Compare.Data assembly which is included in the ZCee Project

The Solution

List<Supplier> originalSuppliers = SampleData.CreateSuppliers(8);
// updatedSuppliers contain all of our modifications. 
// We create them here on the fly and then we are going to modify them.
List<Supplier> updatedSuppliers = SampleData.CreateSuppliers(8);     
// We are going to make some modifications to our data - updatedSuppliers
updatedSuppliers.RemoveAt(0); // Delete a supplier
updatedSuppliers[0].Products[2].Description = "Description Updated";
updatedSuppliers[1].Products[1].Price = 19.99f;
updatedSuppliers[4].Status = SupplierStatus.Active;
updatedSuppliers.Add(SampleData.CreateSupplier(9)); // Create a new one
// Let's compare them 
ZCompareResults results = ZCompare.Compare(originalSuppliers, updatedSuppliers);

results is a container for all the results. We will use this object to query for the results you want to work with.

Let's get only those suppliers that have changes made to them. We use the results.GetResults<T>() method.

// Pass in a reference object, in this case the top level 'originalSuppliers' list 
// and also 'true' to say we only want Suppliers that have changes made to them
var supplierResults = results.GetResults<Supplier>(originalSuppliers, true);
// Lets see if we can do something useful with the results
supplierResults.ForEach(s =>
{
	if (s.Status == ResultStatus.Added)
	{
		Debug.WriteLine("This supplier has been added: " + s.ChangedToValue.Name);
	}
	else if (s.Status == ResultStatus.Deleted)
	{
		Debug.WriteLine("This supplier is deleted: " + s.OriginalValue.Name);
	}
	else
	{
		Debug.WriteLine("This supplier has been changed: " + s.ChangedToValue.Name);
	}
});

--- Debug Output ---
This supplier is deleted: Complicated Cow
This supplier has been changed: Cloudy Cat
This supplier has been changed: Intelligent Duck
This supplier has been changed:  Stormy Knife Company
This supplier has been added: The Cheeky Panda Company

Note how each result has a Status flag indicating if the Supplier has been added, deleted or modified. Also, each result has an OriginalValue and a ChangedToValue property which reference the original object before changes and the modified object respectively.

So, perhaps if we were using a micro ORM...
    supplierResults.ForEach(s =>
{
	if (s.Status == ResultStatus.Added)
	{
            ORM.Insert(s.ChangedToValue);
	}
	else if (s.Status == ResultStatus.Deleted)
	{
            ORM.Delete<Supplier>(s.OriginalValue.ID);
	}
	else
	{
            ORM.Update(s.ChangedToValue);                    
	}
});

Of course things are rarely this simple. We have a Supplier object with a list of Products and a dictionary of Addresses as properties. These properties may of changed and we want to work with them as 'Products' and 'Address' types.

So let's go again, recreate our data and modify it again a bit differently.

originalSuppliers = SampleData.CreateSuppliers(8);            
updatedSuppliers = SampleData.CreateSuppliers(8);
updatedSuppliers[2].Addresses["Head Office"].PostCode.Inner = "PC99"; // Modify an address PostCode for a supplier
updatedSuppliers[2].Products.RemoveAt(0); // Delete a product from this supplier
updatedSuppliers[2].Products.Add(SampleData.CreateProduct(20)); // Add a product to this supplier
updatedSuppliers[2].Products[2].ImageData = new byte[2] { 34, 36 }; // Change a property of a product
updatedSuppliers[4].Addresses.Add("Northern HQ", SampleData.CreateAddressList(3)[0]); // Add an Address
updatedSuppliers[4].Products.Add(SampleData.CreateProduct(21)); // Add a product to this supplier
updatedSuppliers[4].Products[1].Code = new ProductCode { Category = 'K', ProductID = 923 }; 
// Using the same code from before...
results = ZCompare.Compare(originalSuppliers, updatedSuppliers);
supplierResults = results.GetResults<Supplier>(originalSuppliers, true);

We have all the modified suppliers in supplierResults. We can now loop through each one and see if they each have changes to their products.

Note how we use the results.GetResults<Product>() method and pass in 'Products' property of each individual supplier to get the product results for each supplier.

supplierResults.ForEach(s =>
{
    // We can get the product modifications for each supplier like this.
    // Use the GetResults() method and pass in the reference object 
    var productResults = results.GetResults<Product>(s.OriginalValue.Products, true); 
    Debug.WriteLine("Product changes for Supplier: " + s.OriginalValue.Name);
    productResults.ForEach(p => Debug.WriteLine(p.GetSummary()));
});

The GetSummary() method on each result is useful for verbosely showing changes in a user readable form.

Notice how we are only getting the results for 'Products' and not for 'Addresses'.

--- Debug Output ---
ListItem Deleted -  'Zaybu.Compare.Data.Product'
ListItem Changed
Unique ID Field NoChange
Description NoChange
Price NoChange
Inventory Code NoChange
Code NoChange
	ProductID NoChange
	Category NoChange
ImageData Changed - from 'F4-DD-85-CF-CE-1E-E2-D1-31-71-65' to '22-24'
ListItem Added -  'Zaybu.Compare.Data.Product'
ListItem Changed
Unique ID Field NoChange
Description NoChange
Price NoChange
Inventory Code NoChange
Code Changed
	ProductID Changed - from '7' to '923'
	Category Changed - from 'G' to 'K'
ImageData NoChange
ListItem Added -  'Zaybu.Compare.Data.Product'

We have another method to get results.

The GetResultsDeep<T>() method takes a reference object and will get all the changes in the object tree from the supplied reference object (originalSuppliers in this case) and for the <T> type.

var allProductResults = results.GetResultsDeep<Product>(originalSuppliers, true);
allProductResults.ForEach(p => {
	if (p.Status == ResultStatus.Added) Debug.WriteLine("Added: " + p.ChangedToValue.Description);
	else if (p.Status == ResultStatus.Deleted) Debug.WriteLine("Deleted: " + p.OriginalValue.Description);
	else Debug.WriteLine("Changed: " + p.ChangedToValue.Description);
}); 

Here we have all product changes for all suppliers.

--- Debug Output ---
Deleted: Fabulous Elevator
Changed: Pinball
Added: Refittable Nailfile
Changed: Emergency Electromagnetic Retrostinker
Added: Ultrasonic Home Economics Biotargeter
 

This shows that with a combination of GetResults<T>(), GetResultsDeep<T>() and the reference object passed into these methods we can work with our results at any level of the object tree we wish.

Below are some brief examples of working with different types including a Dictionary and a struct.


// In exactly the same way we can work with Address objects which is a Dictionary            
var addressDictionaryResults = results.GetResultsDeep<Dictionary<string, Address>>(originalSuppliers, true);
addressDictionaryResults.ForEach(a => {
	Debug.WriteLine(a.GetSummary());
});
// ...also with a deeply nested PostCode type
var postCodeResults = results.GetResultsDeep(originalSuppliers, true);
postCodeResults.ForEach(a => {
	Debug.WriteLine(a.GetSummary());
});
// ...and also ProductCode which is a struct.
var productCodeResults = results.GetResultsDeep<ProductCode>(originalSuppliers, true);
productCodeResults.ForEach(a => {
	Debug.WriteLine(a.GetSummary());
});

Summary

Hopefully the power of the GetResults<T>() and GetResultsDeep<T>() methods is now clear. Combined with the OriginalValue, ChangedToValue and the Status flag properties of results they form the core of ZCompare functionality. I hope this has shown how you can work with your comparison results in a selective and targetted way. Using the reference object passed into these methods provides a context which allows manageable results from what can be an overwhelming result set. There is much more to ZCompare, such as the ability to Ignore properties, types and namespaces as well as custom comparitors and key fields. Take a look at this tutorial for more information on these features. All of this is designed to allow you to compare objects how you want and get the results you need and can work with effectively.