Shrinking data objects

Quite often, our applications will have fairly sizable data objects, with dozens, or even hundreds, of properties. If we were to load all of the properties for each data object when we have thousands of them, our application would slow down and possibly even run out of memory.

We might think that we can save on RAM by simply not populating all of the property values; however, if we use the same classes, we'll soon find that even the default or empty values for these properties may consume too much memory. In general, and with a few exceptions, unset properties take the same amount of RAM as set properties.

If our data model object has a very large number of properties, one solution would be to break it down into much smaller pieces. For example, we could create a number of smaller, sub product classes, such as ProductTechnicalSpecification, ProductDescription, ProductDimension, ProductPricing, and more.

Rather than building one giant View to edit the whole product, we could then provide a number of smaller Views, perhaps even accessible from different tabs within the same View. In this way, we would be able to just load the ProductDescription objects for the user to select from and then load the individual sections of the product in each sub View.

There is a significant performance increase to be gained by this method, as binding to a single object with a great many properties can take up to four times longer than binding to a great many objects with fewer properties.

One alternative to breaking our data objects into smaller pieces would be to use the concept of thin data objects. For example, imagine that our Product class had dozens of properties and that we had thousands of products. We could create a ThinProduct class that contains only the properties that would be used to identify the full data object to load when selected and those displayed in the product collection.

In this case, we might simply need two properties in our ThinProduct class, a unique identification property, and a display name property. In this way, we can reduce the memory footprint of our products by a factor of 10 or even more. This means that they can be loaded from the database and displayed in a fraction of the time of the full Product objects.

In order to facilitate easy transferal between the Product and ThinProduct classes, we can add constructors into each class that accepts the other type and updates the relevant properties:

using System; 
 
namespace CompanyName.ApplicationName.DataModels 
{ 
  public class ThinProduct : BaseDataModel 
  { 
    private Guid id = Guid.Empty; 
    private string name = string.Empty; 
 
    public ThinProduct(Product product) 
    { 
      Id = product.Id; 
      Name = product.Name; 
    } 
 
    public Guid Id 
    { 
      get { return id; } 
      set { if (id != value) { id = value;
        NotifyPropertyChanged(); } } 
    } 
 
    public string Name 
    { 
      get { return name; } 
      set { if (name != value) { name = value;
        NotifyPropertyChanged(); } } 
    } 
 
    public override string ToString() 
    { 
      return Name; 
    } 
  } 
} 

The properties in this ThinProduct class basically mirror those from the Product class that we saw earlier, but only the ones that are used to identify each instance. A constructor is added that takes an input parameter of type Product to enable easy transferal between the two. A similar constructor is added to the Product class, but takes an input parameter of type ThinProduct:

public Product(ThinProduct thinProduct) : this() 
{ 
  Id = thinProduct.Id; 
  Name = thinProduct.Name; 
} 

The idea is that we have a View Model that displays a large number of products and in code, we actually load a large number of these much lighter ThinProduct instances. When the user selects one of the products to view or edit, we use the identification number of the selected item to then load the full Product object that relates to that identifier.

Given a base collection of these ThinProduct instances in a property named Products, we could achieve this as follows. First, let's bind our collection to a ListBox control:

<ListBox ItemsSource="{Binding Products}" 
  SelectedItem="{Binding Products.CurrentItem}" ... /> 

When the user selects a product from the list, the collection's CurrentItem property will hold a reference to the selected item. If we attach a handler to the collection's CurrentItemChanged delegate when it is first loaded, we can be notified when the item is selected.

At that point, we can load the full Product object using the identifier from the selected ThinProduct instance and output the associated feedback to the user:

private void Products_CurrentItemChanged(ThinProduct oldProduct,  
  ThinProduct newProduct) 
{ 
  GetDataOperationResult<Product> result =  
    await Model.GetProductAsync(newProduct.Id); 
  if (result.IsSuccess) Product = result.ReturnValue; 
  else FeedbackManager.Add(result, false); 
} 

In the next section, we'll find out how we can display our large collections more efficiently using collection controls, rather than having to break up our large classes into smaller classes or create associated thin data objects.