Click here to Skip to main content
15,867,756 members
Articles / General Programming / Architecture

Entity Factory Design Pattern for .NET

Rate me:
Please Sign up or sign in to vote.
4.84/5 (26 votes)
4 Apr 2012CPOL7 min read 59.7K   429   86   14
An entity pattern that allows for extremely high performance.

Introduction

Much like other factory patterns, which allow you to create deriving objects from a base class, the Entity Factory pattern allows you to create deriving entities from a base entity or ‘element’. The element contains the rules of a particular piece of data along with a data wrapper that contains the data itself. This allows for extremely high performance when validating or analyzing an entity object.

In order to achieve this level of performance, we need to define our entities a little different than standard practices. Instead of the property getters and setters working with a private field within the entity class, ‘rule’ fields work with a Hashtable within the BaseHash class and ‘entity’ fields work with the Elements collection within the EntityAbstract class. It’s not a difficult adjustment to make and well worth the effort. In addition to performance, this pattern offers a simple way to maintain data constraints across all your applications.

UML Class Diagram

Image 1

The BaseHash Class

When casting one entity to another, the property values are not populated in the new entity. This can be overcome by including a constructor in the deriving class that accepts an instance of the base class as a parameter. The deriving class then passes the parameter to a constructor on the base class.

C#
public ElementBaseData(ElementBase elementBase) : base(elementBase) 

public ElementBase(ElementBase elementBase) 

Within the base class constructor, you can either hard code the fields to be set to the values of the parameter or use Reflection to iterate through the fields in the parameter and populate the corresponding fields in the new object. The first option is very fast, but also very rigid. The latter option gives you the generic flexibility, but is very slow. Not only slow, reflection will slow down disproportionately to the first approach, as more fields are added.

Benchmarks (100k iterations)

1 field

10 fields

Hard Coded ~20msHard Coded ~50ms
Reflection ~500msReflection ~2800ms

By using a Hashtable to manage the field values, we can get the best of both approaches. Now, the constructor on our ElementBase looks like this.

C#
public ElementBase(ElementBase elementBase) 
{ 
    Hash = elementBase.Hash; 
} 

Our benchmarks come in a little higher than the hard coded approach (1 field = ~110ms), but as the number of fields grow, the hash approach is able to handle it more efficiently than either of the other approaches (10 fields = ~210ms). Ten fields in less than two times the duration of one field.

To begin, we’ll need to create our BaseHash class. This simply consists of a property with a Hashtable type and a method called InitializeProperties. The purpose of the InitializeProperties method will be shown in the next section; The ElementBase Class.

C#
public class BaseHash 
{ 
    private Hashtable _hash = new Hashtable(); 
    public BaseHash() 
    { 
    } 
    public Hashtable Hash 
    { 
        get { return _hash; } 
        set { _hash = value; } 
    } 
    protected void InitializeProperties(Type enumerator) 
    { 
        int i = 0; 
        Enum.GetNames(enumerator).ToList().ForEach(e => _hash.Add(i++, null)); 
    } 
} 

The ElementBase Class

Data constraints are stored in-process on the ElementBase class. The example below contains only a small subset of fields. You may have additional requirements for your constraints like ‘IsNumeric’ or ‘MatchCode’. You can simply add these properties to the ElementBase and define their behavior in the ElementBaseData class. For simplicity, I’m only going to create a few fields.

Notice the way the property getters and setters are written in the example below. They work against the Hashtable in BaseHash rather than private fields. This will give us the speed and flexibility mentioned in the previous section. We need to initialize the properties when the instance is created. Otherwise, we will get errors from the property, because the index has not been setup in the Hashtable.

C#
public class ElementBase : BaseHash, IBase 
{ 
    public enum ElementBaseFields : int 
    { 
        ID, 
        Name, 
        MaxLength, 
        MinLength 
    } 
    public ElementBase() 
    { 
        InitializeProperties(typeof(ElementBaseFields)); 
    } 
    public ElementBase(ElementBase elementBase) 
    { 
        Hash = elementBase.Hash; 
    } 
    [DataMember] 
    public int? ID 
    { 
        get 
        { 
            if (Hash[(int)ElementBaseFields.ID] == null) 
                return null; 
            return int.Parse(Hash[(int)ElementBaseFields.ID].ToString()); 
        } 
        set { Hash[(int)ElementBaseFields.ID] = value; } 
    } 
    [DataMember] 
    public string Name 
    { 
        get 
        { 
            if (Hash[(int)ElementBaseFields.Name] == null) 
                return null; 
            return Hash[(int)ElementBaseFields.Name].ToString(); 
        } 
        set { Hash[(int)ElementBaseFields.Name] = value; } 
    } 
    [DataMember] 
    public int? MaxLength 
    { 
        get 
        { 
            if (Hash[(int)ElementBaseFields.MaxLength] == null) 
                return null; 
            return int.Parse(Hash[(int)ElementBaseFields.MaxLength].ToString()); 
        } 
        set { Hash[(int)ElementBaseFields.MaxLength] = value; } 
    } 
    [DataMember] 
    public int? MinLength 
    { 
        get 
        { 
            if (Hash[(int)ElementBaseFields.MinLength] == null) 
                return null; 
            return int.Parse(Hash[(int)ElementBaseFields.MinLength].ToString()); 
        } 
        set { Hash[(int)ElementBaseFields.MinLength] = value; } 
    } 
} 

The ElementBaseData Class

The ElementBaseData is a data wrapper for the ElementBase. Here we can create our Data property in the traditional way. We’ll make a private variable object field called _data and make a property called Data that uses it.

One important thing to consider with this pattern is cloning. If we we’re to just clone this item, it would continue to share the Hashtable with the originator. This could cause big problems. It’s important to include a Clone method that will also clone the Hashtable for us.

I’ve also included a Validation method. This will demonstrate the high performance we get from this pattern. You can see how we make use of the properties exposed in the ElementBase to perform our validation.

The exceptions exposed by the Validation method are strings rather than Exception objects. Since the problem isn’t from the stack, there’s no need for the overhead of an Exception object.

C#
public class ElementBaseData : ElementBase, IBaseData, ICloneable 
{ 
    private object _data = null; 
    public ElementBaseData() : base() 
    { 
    } 
    public ElementBaseData(ElementBase elementBase) 
        : base(elementBase) 
    { 
    } 
    [DataMember] 
    public object Data 
    { 
        get { return _data; } 
        set { _data = value; } 
    } 
    [OperationContract] 
    public object Clone() 
    { 
        ElementBaseData elementBaseData = this.MemberwiseClone() as ElementBaseData; 
        elementBaseData.Hash = Hash.Clone() as Hashtable; 
        return elementBaseData; 
    } 
    [OperationContract] 
    public List<string> Validate() 
    { 
        List<string> exceptions = new List<string>(); 
        if (MaxLength != null && _data.ToString().Length > MaxLength) 
            exceptions.Add(String.Format("Property: {0}{1}{2}", Name, Environment.NewLine, "Data is too long")); 
        if (MinLength != null && _data.ToString().Length < MinLength) 
            exceptions.Add(String.Format("Property: {0}{1}{2}", Name, Environment.NewLine, "Data is too short")); 
        return exceptions; 
    } 
} 

The EntityAbstract Class

Now we’re ready to create our abstract class for our entities. The properties values of our entities will be stored and retrieved from the Elements property on the EntityAbstract. The Elements property is a collection of ElementBaseData objects. The other property on the EntityAbstract is the Exceptions property. This is a collection of exception strings generated by the Validation method on the ElementBaseData objects.

We run into the same cloning issue that we saw in the ElementBaseData class. To avoid sharing the same collection of elements, we will need to have a Clone method that will clone the elements for us too. Also, in the EntityAbstract class, we’ll want to create a Validation method. This will give each of our entities the functionality for quick validation of all the elements within the entity. After validating, we can call the Exceptions property to get any exceptions that were generated.

C#
public abstract class EntityAbstract : ICloneable 
{ 
    private List<ElementBaseData> _elements = new List<ElementBaseData>(); 
    private List<string> _exceptions = new List<string>(); 
    public List<ElementBaseData> Elements 
    { 
        get { return _elements; } 
        set { _elements = value; } 
    } 
    public List<string> Exceptions 
    { 
        get { return _exceptions; } 
        set { _exceptions = value; } 
    } 
    public virtual object Clone() 
    { 
        List<ElementBaseData> elements = new List<ElementBaseData>(); 
        Elements.ForEach(e => elements.Add(e.Clone() as ElementBaseData)); 
        return elements; 
    } 
    public virtual bool Validate() 
    { 
        Exceptions.Clear(); 
        return Elements.FindAll(e => !Validate(e)).Count == 0; 
    } 
    private bool Validate(ElementBaseData elementBaseData) 
    { 
        List<string> elementExceptions = elementBaseData.Validate(); 
        elementExceptions.ForEach(ex => _exceptions.Add(ex)); 
        return elementExceptions.Count == 0; 
    } 
} 

Entity Classes and the ElementIndex

The ElementIndex is an in-process repository of element rules. In this example I use a static class, but a Singleton will work just as well. If you need to track usage or want to load balance the ElementIndex, you’ll need to use the Singleton Pattern. These aren’t needed for this demonstration, so I choose to show the static class to keep it simple.

One nice feature of the ElementIndex is that it allows you to maintain your data standards across entities and systems. For example, you may have a system that can accept last names that are 25 characters long and have a different system that can accept last names that are 35 characters long. With the ElementIndex you can make sure the least common denominator is always used and avoid truncation after the data is collected.

C#
public static class ElementIndex 
{ 
    public readonly static ElementBase FirstName = new ElementBase() 
    { 
        Name = "First Name", 
        MinLength = 1, 
        MaxLength = 25 
    }; 
    public readonly static ElementBase LastName = new ElementBase() 
    { 
        Name = "Last Name", 
        MinLength = 1, 
        MaxLength = 25 
    }; 
    public readonly static ElementBase Age = new ElementBase() 
    { 
        Name = "Age", 
        MinLength = 1, 
        MaxLength = 3 
    }; 
} 

Now we can begin creating our entities. For this demonstration I will create a TestEntity class extending the EntityAbstract. In the constructor I will add each field to the Elements property of the EntityAbstract. The field is an ElementBaseData object built from an ElementBase that is defined in the ElementIndex. Next, I create a property for each element that I have created in the constructor.

Again, I need to consider the clone method. I can handle this by overriding the base clone while still using the base method to rebuild my elements collection.

C#
public class TestEntity : EntityAbstract 
{ 
    public enum TestEntityFields : int 
    { 
        FirstName, 
        LastName, 
        Age 
    } 
    public TestEntity() 
    { 
        Elements.Add(new ElementBaseData(ElementIndex.FirstName) 
        { 
            ID = (int)TestEntityFields.FirstName 
        }); 
        Elements.Add(new ElementBaseData(ElementIndex.LastName) 
        { 
            ID = (int)TestEntityFields.LastName 
        }); 
        Elements.Add(new ElementBaseData(ElementIndex.Age) 
        { 
            ID = (int)TestEntityFields.Age 
        }); 
    } 
    public string FirstName 
    { 
        get { return Elements[(int)TestEntityFields.FirstName].Data.ToString(); } 
        set { Elements[(int)TestEntityFields.FirstName].Data = value; } 
    } 
    public string LastName 
    { 
        get { return Elements[(int)TestEntityFields.LastName].Data.ToString(); } 
        set { Elements[(int)TestEntityFields.LastName].Data = value; } 
    } 
    public int Age 
    { 
        get { return int.Parse(Elements[(int)TestEntityFields.Age].Data.ToString()); } 
        set { Elements[(int)TestEntityFields.Age].Data = value; } 
    } 
    public override object Clone() 
    { 
        List<ElementBaseData> elements = base.Clone() as List<ElementBaseData>; 
        TestEntity testEntity = this.MemberwiseClone() as TestEntity; 
        testEntity.Elements = elements; 
        return testEntity; 
    } 
} 

Testing the Pattern

I’ve put together a simple console application to test and benchmark the performance of our pattern. It will iterate through the elements in our TestEntity class, asking the user for inputs on each. Once all the inputs are collected, it will perform a validation 1,000 times and report the duration in milliseconds. It will also report whether the data is valid or display all the errors that it encountered.

C#
class Program 
{ 
    private static void GetUserInput(ElementBaseData elementBaseData) 
    { 
        Console.Write(String.Format("{0}: ", elementBaseData.Name)); 
        elementBaseData.Data = Console.ReadLine(); 
    } 
    static void Main(string[] args) 
    { 
        while(true) 
        { 
            TestEntity testEntity = new TestEntity(); 
            testEntity.Elements.ForEach(e => GetUserInput(e)); 
            System.Diagnostics.Stopwatch bench = new System.Diagnostics.Stopwatch(); 
            bench.Start(); 
            int iEnd = 1000; 
            for (int i = 0; i < iEnd; i++) 
            { 
                testEntity.Validate(); 
            } 
            bench.Stop(); 
            Console.WriteLine(String.Format("Validated {0} times in {1} milliseconds.", 
                              iEnd.ToString(), bench.ElapsedMilliseconds.ToString())); 
            if (testEntity.Validate()) 
                Console.WriteLine("Data is valid"); 
            else 
                testEntity.Exceptions.ForEach(ex => Console.WriteLine(ex)); 
        } 
    }
}

Conclusion

This pattern offers two major advantages; high performance and the ability to consolidate your constraint variables. It also creates a framework, ready for more advanced functionality like persistence and reporting.

Although we’re changing the way the getters and setters are working with their values, the properties and methods of the entities are still exposed the same way traditional entities expose their members. This will allow you to continue using Interfaces and LINQ to Entities as you have before. Any Reflection that uses GetField will need to be modified to use the Elements property. This will give you much better performance.

In my next article, I will demonstrate using a Memento Pattern that integrates perfectly with the Entity Factory Pattern giving your applications 'Ctrl-Z', 'Ctrl-Y' functionality.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Ready EDI
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionlambda expressions Pin
P0110X13-Apr-12 11:04
professionalP0110X13-Apr-12 11:04 
AnswerRe: lambda expressions Pin
chuckforest14-Apr-12 7:29
chuckforest14-Apr-12 7:29 
GeneralRe: lambda expressions Pin
P0110X16-Apr-12 5:37
professionalP0110X16-Apr-12 5:37 
QuestionMaxLength does unnecessary conversions. Pin
Paulo Zemek4-Apr-12 3:09
mvaPaulo Zemek4-Apr-12 3:09 
AnswerRe: MaxLength does unnecessary conversions. Pin
chuckforest4-Apr-12 3:15
chuckforest4-Apr-12 3:15 
QuestionWhy do you always cast to int? Pin
Paulo Zemek4-Apr-12 2:51
mvaPaulo Zemek4-Apr-12 2:51 
AnswerRe: Why do you always cast to int? Pin
chuckforest4-Apr-12 3:12
chuckforest4-Apr-12 3:12 
GeneralRe: Why do you always cast to int? Pin
Paulo Zemek4-Apr-12 3:18
mvaPaulo Zemek4-Apr-12 3:18 
GeneralRe: Why do you always cast to int? Pin
chuckforest6-Apr-12 10:11
chuckforest6-Apr-12 10:11 
GeneralRe: Why do you always cast to int? Pin
Paulo Zemek6-Apr-12 10:28
mvaPaulo Zemek6-Apr-12 10:28 
QuestionNice Pin
R. Giskard Reventlov3-Apr-12 16:37
R. Giskard Reventlov3-Apr-12 16:37 
GeneralWe should all take a lesson here Pin
K.C. Jones3-Apr-12 10:02
K.C. Jones3-Apr-12 10:02 
SuggestionSample Code? Pin
digitalMoto3-Apr-12 7:16
digitalMoto3-Apr-12 7:16 
GeneralRe: Sample Code? Pin
chuckforest3-Apr-12 7:53
chuckforest3-Apr-12 7:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.