August 3, 2010

Cleaning up my data access layer with enumerations, attributes, reflection and generics

I thought I'd continue where I left off in my previous blog post. In this post, I will discuss cleaning up enumerations that are tied to database values.

In the past, I've created a helper method for each of my enumerations, providing helper methods that convert back and forth between enum values and their corresponding display and database values. While this has served me fairly well in the past, it's always kind of bugged me. Today I thought I'd actually sit down and figure out a better way, and hey presto, generics came to my rescue once more.

To get you up to speed, I started with this kind of pattern in the past:

public enum DemoEnumType{
  Value1,
  Value2,
  Value3
}

public class DemoEnumHelper
{
  public string GetDbValue(DemoEnumType value)
  {
    switch(value)
    {
      case Value1:
        return "det1";
      case Value2:
        return "det2";
      case Value3:
        return "det3";
    }
  }
  public string GetDisplayValue(DemoEnumType value)
  {
    switch(value)
    {
      case Value1:
        return "Custom Display Value 1";
      case Value2:
        return "Custom Display Value 2";
      case Value3:
        return "Custom Display Value 3";
    }
  }
  public DemoEnumType ParseFromDbValue(string dbValue)
  {
    switch(value)
    {
      case "det1":
        return DemoEnumType.Value1;
      case "det2":
        return DemoEnumType.Value2;
      case "det3":
        return DemoEnumType.Value3;
    }
  }
  public DemoEnumType ParseFromDisplayValue(string dispValue)
  {
    switch(value)
    {
      case "Custom Display Value 1":
        return DemoEnumType.Value1;
      case "Custom Display Value 2":
        return DemoEnumType.Value2;
      case "Custom Display Value 3":
        return DemoEnumType.Value3;
    }
  }
}

Now I will make a small disclaimer, these are rapidly produced from an Excel spreadsheet of values that generate both the enum and the helper class, so while this excerpt doesn't necessarily follow the principles of DRY, it performs well and it's quickly generated. But that doesn't mean I'm necessarily happy with the result.

So what can I do about it? Well, first off, I'd prefer if all my values, the enum value itself, the display value and the database value were all held in a more concise manner, something like:

public enum DemoEnum
{
  [DisplayValue("Custom Display Value 1")]
  [DatabaseValue("det1")]
  Value1,
 
  [DisplayValue("Custom Display Value 2")]
  [DatabaseValue("det2")]
  Value2,
 
  [DisplayValue("Custom Display Value 3")]
  [DatabaseValue("det3")]
  Value3
}

In order to do this, I've gotta create a couple of classes that inherit from the Attribute class that allows me to decorate my enum values with such features:

public interface IAttribute
{
  object Value{ get; }
}
public class DisplayValue : Attribute, IAttribute
{
  public object Value{ get; private set; }
  public DisplayValue(object value)
  {
    this.Value = value;
  }
}
public class DatabaseValue : Attribute, IAttribute
{
  public object Value{ get; private set; }
  public DatabaseValue(object value)
  {
    this.Value = value;
  }
}

Sweet, so now I've got my enum and all my custom values in one place instead of scattered across my enum and a corresponding helper method. However, that doesn't help me retrieve the attribute values, nor does it help me get the corresponding enum value for any given attribute.

Here's where it starts getting complicated...ish. I'll start off at the bottom end, I want to be able to grab the value of one of the attributes, I'm going to write a helper method that will grab either one (or indeed any other) of the custom attributes.

We're going to need a little bit of reflection, so don't forget to add using System.Reflection; to the file containing your helper class...

public static class EnumHelper
{
  public static object GetAttribute<TEnum, TAttribute>(TEnum enumValue)
  {
    Type t = typeof(TEnum);
    FieldInfo fi = t.GetField(enumValue.ToString());
    object attr = fi.GetCustomAttributes(typeof(TAttribute), false)[0];
    return (IAttribute)Convert.ChangeType(attr, typeof(TAttribute));
  }
  public static object GetAttributeValue<TEnum, TOutput>(TEnum enumValue)
  {
    return GetAttribute<TEnum, TOutput>(enumValue).Value;
  }
}

I can very easily use these methods to get either of my attributes and their values using:

string dispVal = 
  (string)EnumHelper
    .GetAttributeValue<DemoEnum, DisplayValue>(DemoEnum.Value3);

or

string dbVal = 
  (string)EnumHelper
    .GetAttributeValue<DemoEnum, DatabaseValue>(DemoEnum.Value2);

Perfect. So now what about the other way? What if I have an attribute value, and I need to parse out an enum from it? Well, even that isn't so difficult with a couple of tricks:

public static object Parse(Type enumType, 
                           Type attributeType, 
                           object attributeValue)
{
  FieldInfo fi = enumType.GetFields();
  /* We start at one because the first field in an enum 
   * isn't relevant to us for this purpose */
  for (int i = 1; i < fi.Length; i++) 
  {
    IAttribute attr = 
      (IAttribute)fi[i].GetCustomAttributes(attributeType, false)[0];
    if (attributeValue.Equals(attr.Value))
      return fi[i].GetValue(fi[i]);
  }
  return null;
}

This will allow us to parse an enum value from a specified attribute value like so:

DemoEnum enumVal = (DemoEnum)EnumHelper.Parse(
  typeof(DemoEnum), 
  typeof(DatabaseValue), 
  "det1");

Or

DemoEnum enumVal = (DemoEnum)EnumHelper.Parse(
  typeof(DemoEnum), 
  typeof(DisplayValue),
  "Custom Display Value 1");

So now we can convert between enum values and their corresponding attributes and attribute values, the only thing left to handle is converting from one attribute to another. This can very easily be achieved by converting from the known attribute to its corresponding enumeration value, and then back to the desired attribute value:

public static object GetAttributeValue<TEnum, TMatchAttribute, TReturnAttribute>(
  object matchAttributeValue)
{
  TEnum obj = (TEnum)Parse(typeof(TEnum), typeof(TMatchAttribute), matchAttributeValue);
  return GetAttribute<TEnum, TReturnAttribute>(obj).Value;
}

Now all we've got to do is add a couple of methods to give us some syntactic sugar for our basic attributes which we commonly use and we're done like dinner:

public static object GetDisplayValue<TEnum>(TEnum enumValue)
{
  return GetAttribute<TEnum, DisplayValue>(enumValue).Value;
}
public static object GetDisplayValue<TEnum>(string databaseValue)
{
  return GetAttributeValue<TEnum, DatabaseString, DisplayString<(databaseValue);
}

And I'm sure you can do the same for your database values without any fuss, so I'll leave that as an exercise for you.

You can download the example project from my public dropbox at http://dl.dropbox.com/u/3029830/Prototypes/EnumHelpers.zip

10 comments:

  1. Hey Ben,

    I've always wanted to create the Enums Dynamically from the database using System.Reflection.Emit.EnumBuilder.

    The only thing I'm afraid of is how often to generate the Enum, and if it's really effective as it is dynamically generating a file (or in memory) which means to use the Enum you'd have to retreive it from production in order to update code.

    Just my two cents.

    Cory "SyntaxC4" Fowler

    ReplyDelete
  2. Interesting approach, I may have to try this on my next project as I've always disliked the way I currently do this (which was similar to your original method).

    ReplyDelete
  3. Hey Ben,

    A couple things:

    1. Most importantly, double check your link, I'm under the impression DropBox links are read/write, so you might want to double check it.

    2. Your approach is definitely cleaner than mine, I've thought about doing this quite a few times, since it's sooo much cleaner. I never did because of the 'reflection is expensive' impression that everybody has. But you know; a performance test might be a really good blog post.

    3. I'll try to find the code that generates the enum helper tonight. I should post my own thoughts on this as well.

    Thanks for the post

    ReplyDelete
  4. @Cory

    I've been down this road before and I like the approach to generate the enums, it does have its drawbacks, as you suggest, when should you run the updates etc.? My POV on that is like with any other code change, you make the change at design time not runtime. I haven't performance tested it, but my gut tells me that could get expensive in a hurry - especially if you're generating to file.

    One drawback is that with auto-generation, depending on your design you may be tempted to go back to:

    public enum DemoEnum as string
    {
      Value1 = "Display Value 1",
      Value2 = "Display Value 2",
      Value3 = "Display Value 3"
    }

    Because you've now replaced the enum values with fairly lengthy strings, you've got no means to store a small value in the database which may hinder performance in other areas, so this is something that needs to be taken into account.

    I do think however that if you had a table containing the enum value, the display value and the storage value, you could auto-generate the enum with the custom attributes for each of the values.

    Now you've got a whole new section of your database to manage, which could add a significant amount of work to your design:

    /* Storage for each of the C# data types so we know what data type
    * the enum should be, i.e. int, bit, byte, string etc. */
    Table Data_Types(
      DT_Key Int Identity(1,1) Primary Key,
      DT_Name NVarChar(20)  
    )

    /* Storage for each of the accessor types so we know whether the
    * enum should be public, private, protected */
    Table Accessor_Types(
      AT_Key Int Identity(1, 1) Primary Key,
      AT_Name NVarChar(20)
    )

    /* Storage for Enum headers */
    Table Enums(
      Enum_Key Int Identity(1,1) Primary Key,
      Enum_Accessor Int,
      Enum_Name NVarChar(128),
      Enum_DataType Int Foreign Key References DataTypes(DataType_Key)
    )

    /* Storage for each of the values for each of the enums */
    Table Enum_Values(
      EV_Key Int Identity(1,1) Primary Key,
      Enum_Key Int Foreign Key References Enums(Enum_Key),
      EV_ValueName NVarChar(128),
      EV_Value NVarChar(255) /* Cast at runtime */
    )

    /* Storage for each of the custom attributes for each of the enums */
    Table Enum_Custom_Attributes(
      ECA_Key Int Identity(1,1) Primary Key,
      EV_Key Int Foreign Key References EnumValues
      ECA_Name NVarChar(128),
      ECA_Value NVarChar(255)
    )

    What do you think? It's not completely out of the question, but still something that needs consideration.

    ReplyDelete
  5. @Cory,

    I forgot the namespace for the enum in the enum table, so you'd need another column for that. You'd also need a namespace column in your custom attributes table if it was going to differ from the namespace of your enum.

    ReplyDelete
  6. @whileicompile

    Your point about performance of reflection is noted and should be considered. However, in my experience enums are rarely searched in this manner in loops of a significant number of iterations to matter. *Most* of the time, your enums are likely to be less than 10 values, and most of the time you're not running them through a high number of iterations, if any.

    In a situation where you're needing to parse large enums through many iterations, then potential performance hit of reflection should be considered. However, if you have to parse an enum of less than 10 values each having 2 custom attributes on a save or load cycle, it shouldn't make a significant enough impact to be of concern.

    Like any business decision, you have to weigh both sides of the argument. The code base has decreased significantly vs. a few milliseconds performance hit to the end user in situations that most likely aren't going to matter in most cases.

    I will run some performance analysis when I get a chance and write a follow up blog post on it.

    ReplyDelete
  7. @Cory,

    The other down side to that approach is that now you're starting to tightly couple your database with your business model which is a fairly big no-no. Separation of concerns' philosophy dictates that your database should sit independently of the software that sits on top of it.

    ReplyDelete
  8. I was thinking more about just the performance overhead of looking it up. The attribute strategy is a way cleaner strategy that's for sure, I'm just wondering how much of a performance hit you take. Because yeah, I've heard "Reflection is expensive" so many times before.

    This SO Answer
    http://stackoverflow.com/questions/224232/what-is-the-cost-of-reflection/224245#224245
    indicates it's about2.5-3 times the cost for property modification. Since you are just talking about looking up data, I wonder if it's even less. So to me, that might not be so bad.

    I believe the concern in the answer about junior devs is mostly irrelevant, since everything can be isolated to a single class, which a junior will never have to touch, if that's really a concern.

    Anyway, I'd definitely like to hear your thoughts on performance. I'd love to switch to attributes.

    ReplyDelete
  9. @whileicompile

    Yeah, I've seen the stack overflow article you mentioned - it can get expensive really quickly if it's not used carefully.

    In situations where you're having to parse large enums through large numbers of iterations - i.e. when you're creating lists of objects, it could be expensive if not handled sensitively. For instance, you may want to cache each of the enum values with their corresponding attribute values into a table for each enum value and then grab the cached values once for the creation of each object rather than using your enum helper to do all the heavy lifting in that situation.

    Of course, even for lists of items, you're likely only going to be after a single page of items - probably 10 items per page, let's say (for argument's sake) your enum will probably have (at most) 5 values and it would probably only have 2 custom attributes per value. So assuming that every reference to an enum value is via the 2nd attribute on the last enum value, you're still only looking at a maximum hit of 100 comparisons. I'd say it's unlikely to matter significantly in this situation.

    Of course, the performance hit escalates the larger the enum, the more custom attributes you have and the larger number of objects you need to create using it as a reference.

    So if your user suddenly decides (s)he wants to view 100 items per page instead of 10, now you've increased the performance hit tenfold.

    ReplyDelete
  10. @whileicompile

    Plus, if you read the Stack Overflow answer again, it says that "property modification" is 2.5-3.0X slower. We're not doing property modification here, we're just comparing values which isn't such a large performance hit.

    The values are set using bog-standard mainstream C# code X.Property = "Hello World!", so there's no reflection overhead here.

    ReplyDelete