Home » Snippets

Generic Entity Attribute Value Model – A POCO Implementation

25. February 2010 by Juliën Hanssens 0 Comments

The “Comparing-Apples-With-Oranges-Model”Recently we were working on a proof of concept on a Entity Attribute Value (EAV) model in C#. Using just POCO and no help from other tools like Entity Framework. The angle was to create a generic Product class for which every distinct derived instance could have unique properties. And where the base Product class holds properties and some basic functionality which all products inherit. Most importantly, we wanted the compiler and Intellisense to swallow it without the use of the dynamic keyword from the DLR.

A colleague respectfully called it: the “Comparing-Apples-With-Oranges-Model”, because of the totally different parameters per product/category.

The result

So far the theory in a mouthful. In practice we wanted to achieve the functionality as shown in the sample below. Here we create two totally different products, a Car and a Computer. And, like you would expect, both have totally different attributes/parameters.

The result will look like this:

// Create a "Car"
var myCar = new Product<Car>();
myCar.ProductId = 1;
myCar.Parameters.Brand = "BMW";
myCar.Parameters.Model = "320D";
 
// Create a "Computer" with totally different Parameters than the Car
var myComputer = new Product<Computer>();
myComputer.ProductId = 2;
myComputer.Parameters.CPU = "Intel Core i7";
myComputer.Parameters.MemoryAmount = 4;
 
// Call methods which apply to all products
myCar.AddToCart();
myComputer.AddToCart();

Note that the property ProductId is identical over every product type and that the T in Product<T> automatically reflects the correct attributes (Parameters) per distinct type.

The sample above is the actual result and – in a nutshell – only requires one specific generic class to achieve this.

Building the sample

Let’s start with creating the base Product class. Every derived product should also have the AddToCart method. So it makes perfect sense to place this in the base class, just like the ProductId property.

And let us not forget to start naming things conventionally. A “derived product” in technical terms is in this scenario a product category in business terms.  Taking that in notice, here is the base class:

public abstract class Product
{
    /* Define base properties which every derived child will inherit */
    public int ProductId { get; set; }
    public IProductCategory Parameters { get; set; }
 
    /* ... same for the methods */
    public void AddToCart() { throw new NotImplementedException(); }
}
When taking notice of the class above, you will see it is abstract. Logically, we do not want the base class to be initialized as a valid product. We only want to accept true product instances. But more importantly, you will see the Parameters property defined as IProductCategory. The interface is actually a placeholder for the individual products to inherit.

Let’s define this “mightily complex” IProductCategory interface.

public interface IProductCategory { /* Empty by design */}

Easy as cake. Nothing more is required. The IProductCategory interface is just a placeholder for allowing the products (which we’re going to create later on) to have their own attributes as plain old properties.
 
Just one more step to complete before we can start creating some products. And that is we need to extend the base Product class with a concrete implementation. We need to be able to initialize the class and provide a specific type. This is actually where the magic resides:
   1: public class ProductConfigurator<T> : ProductConfigurator
   2:     where T : IProductCategory, new()
   3: {
   4:     private T _Parameters = new T();
   5:     new public T Parameters
   6:     {
   7:         get { return _Parameters; }
   8:         set { _Parameters = value; }
   9:     }
  10: }
Looks simple enough, but let’s take a closer look at some of its subtleties:
  1. Line 1 defines an initializable class, constrained by generics to only classes which implement IProductCategory.
  2. It also initializes the class by default, using the new() statement on line 2, allowing us to provide just the type name and not an initialized instance
  3. Line 5 declares the Parameters property using the new keyword, basically to replace the same member with a concrete instance (polymorphism)

Minor note: We use .NET 2.0 syntax for the property, because Automatic Properties don’t work with the new keyword.

And that’s all there is to it! We can now easily create custom products, i.e. categories, by just implementing the IProductCategory interface.

Full sample

For you lazy kids, here is the end result – copy/past ready and with the dummy products in place (Car and Computer).

public abstract class Product
{
    /* Define base properties which every derived child will inherit */
    public int ProductId { get; set; }
    public IProductCategory Parameters { get; set; }
 
    /* ... same for the methods */
    public void AddToCart() { throw new NotImplementedException(); }
}
 
public class Product<T> : Product
    where T : IProductCategory, new()
{
    private T _Parameters = new T();
    // Note: Original C# 2.0 syntax, because Automatic Properties doesn't work.
    new public T Parameters
    {
        get { return _Parameters; }
        set { _Parameters = value; }
    }
}
 
public interface IProductCategory { /* Empty by design */}
 
public class Car : IProductCategory
{
    public string Brand { get; set; }
    public string Model { get; set; }
}
 
public class Computer : IProductCategory
{
    public string CPU { get; set; }
    public int MemoryAmount { get; set; }
}
Comments are closed