Practical .NET

Working with Enumerated Values in Entity Framework

If you decide on using an Enum with enumerated values in your Entity Framework class, here are the tools you'll need to make it work. But an enumerated value shouldn't be your first choice.

Enumerated values can look very much like a lookup table: Both provide a set of meaningful names that are associated with an underlying value. For example, a lookup table of states might associate the meaningful name of "Missouri" with the underlying value of "MO" (which, you'll notice, no one would ever figure out on their own). Enumerated values do the same thing but are more restrictive: The underlying values must always be an integer value. So, for example, when you define an Enum you can associate a credit status of "Excellent" with an underlying value of 1, but not with a value of "EX."

Lookup tables are data features that establish a relationship between values and descriptive text. Enumerations/Enums are a code feature that allows developers to use unique numeric values in place of strings that may be misspelled or misread (while you can't misspell 1, you can misspell "excellant"). The benefit of Enums is that developers still get the benefit of using meaningful names in their code.

In general, lookup tables are more flexible than enumerations: You can usually bind lookup tables to UI features (like dropdown lists) more easily than you can bind enumerated values; lookup tables can be integrated into SQL statements where Enums can not; and lookup tables can be accessed from stored procedures (again, Enums can not). In addition, while enumerated values are only accessible within a .NET Framework application, lookup tables can be used by non-.NET Framework applications. (I'm told such applications exist.) It's also generally easier to incorporate new values into a lookup table than it is in an enumerated value (you don't have to recompile and redeploy your application because you've changed an entry in a lookup table, for example).

In general, I recommend to my clients that, in their Entity Framework models where they want to convert from codes to meaningful text, they use a lookup table, even with those clients working in a Code First mode. The entity classes that use the values from the lookup tables can incorporate navigation properties that tie to those lookup tables.

Notice that there's nothing stopping you from both setting up an Enum to use in your code and a corresponding lookup table to use in SQL statements, UIs, stored procedures and non-.NET Framework applications. The issue would be making sure that the table and the Enum stay in sync so you should use this strategy only with values that are unlikely to change.

In fact, that's generally considered the best practice for using enumerated values: Use them only where the entries in the Enum are unlikely to change. If you have a case where that rule applies, here's how to use enumerated values in your Entity Framework models.

Defining an Enumerated Property
The first step in using enumerated values is to declare them in an Enum. I might use this enumeration to establish a set of named values for customer credit ratings:

Public Enum CreditStatusTypes
  Excellent
  Good
  Unacceptable
End Enum

With that enumeration defined, I can use it to declare a property in an Entity Framework entity class, like this:

Public Class Customer
  Public Property Id As Integer
  Public Property CustCreditStatus As CreditStatusTypes
  '...more properties...

In a Code First model, a table will be generated for the Customer class, but will not be generated for the Enum. If you're working in a Database First mode then you'll need to ensure that the column in the table is declared as some kind of integer so that it can hold these numeric values (if you're working in a Code First mode, the column will be declared correctly for you).

With that code in place, I can use the Enum's values when working with the Customer object. In a LINQ query, I can use the enumerated value with any property declared using the Enum. This example uses the property in a Where clause:

Dim custBadStatus = From cust In db.Customers
                    Where cust.CustCreditStatus = CreditStatusTypes.Unacceptable
                    Select cust

I can also use the enumerated value to set a property declared with the Enum in updates, as in this example:

Dim cust = (From cust In db.Customers
            Where cust.Id = 123
            Select cust).First
cust.CustCreditStatus = CreditStatusTypes.Excellent
db.SaveChanges

You'll want to make sure what happens in your table when you add a new object and the enumerated property hasn't been set: Is the corresponding column set to Null or does it default to 0 (remember: the column and the property are both really integer values)? It might be a good idea to ensure that your first enumerated value (the one that will be assigned the value 0) isn't a valid value, like this:

Public Enum CreditStatusTypes
  Unset
  Excellent
  '...rest of the Enum's values...

Creating Meaning
My earlier query and update code demonstrates the value of using an enumerated value: the code is obviously easier to understand than if I was testing against an integer value like 2 (for Unacceptable) or setting a property to 0 (for Excellent). Except, of course, under the hood that's exactly what the compiled version of my code is doing. In fact, if I were to go and look at the Customers table in my database I would find that the CustCreditStatus column is filled with 0s, 1s, and 2s.

Furthermore, if you retrieve an enumerated property and display it in your UI, it's the integer value what will be displayed to your users; returned from a Web Service, it's the integer value that will be sent to the client application.

This has some advantages. If, for example, you have a listbox showing all the acceptable credit statuses and have that listbox return a value of 0 when the user selects Excellent, you can use that numeric value to set your enumerated property. It means code like this is perfectly acceptable (assuming that I intend to set the Customer object's credit status to Good):

cust.CustCreditStatus = 1

Leveraging the Enum Object
But you don't have to use the numeric values at all if you become familiar with the Enum object. The Enum object's GetNames member, for example, will give you a collection of the string representations of all the members of the enumeration (in Visual Basic, you must enclose the Enum object's name in square brackets to use it). This code, for example, will load a listbox with the names from the CreditStatusTypes Enum, for example:

Me.ListBox1.DataSource = (From li In [Enum].GetNames(GetType(CreditStatusTypes))
                          Select li).ToArray

And this code will convert the selected text in the listbox back into an acceptable value for the enumeration:

cust.CustCreditStatus = [Enum].Parse(GetType(CreditStatusTypes),
                                     Me.ListBox1.SelectedValue)

On the display side, if you move an enumerated property into a textbox, as in this code:

Me.TextBox1.Text = cust.CustCreditStatus

then all that will be displayed to the user will be the numeric value of the CustCreditStatus. That won't be very helpful to your user, so it's probably useful to add a calculated "helper" property to your entity class that will use the Enum object to convert the numeric value in the enumerated property to a meaningful string. You should define this property as:

  • Readonly because you don't want developers who use it to update the object
  • Decorated with the NotMapped attribute because you don't want Entity Framework trying to tie it to a column in your table

Here's a typical implementation that uses the Enum object's GetName method to convert the value in the CustCreditStatus property into the corresponding Enum name (in C#, you'd use typeof instead of GetType):

Public Class Customer
  Public Property Id As Integer
  Public Property CustCreditStatus As CreditStatusTypes
  <NotMapped>
  Public ReadOnly Property CustCreditStatusName As String
    Get
      Return [Enum].GetName(GetType(CreditStatusTypes), Me.CustCreditStatus)
    End Get
  End Property

With this property in place, I'd use this code to move the resulting name into the textbox:

Me.TextBox1.Text = cust.CustCreditStatusName

As I said, I don't think Enums should be your first choice. But, when you do decide to use an enumerated value, these are the tools you'll need.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube