Welcome to MSDN Blogs Sign in | Join | Help
Tip 23 – How to fake Enums in EF 4

As of right now Enums are not in EF4.

Now we will be listening to your feedback about Beta1, and making some adjustments, so you never know, but at the moment it doesn’t look like they will be supported.

Yesterday though I came up with a workaround that, while a bit of work, is pretty interesting.

Workaround

To get this working you need .NET 4.0 and you need to use POCO classes.

Imagine you have an Enum like this:

public enum Priority

   High,
   Medium,
   Low
}

First step is to create a ComplexType with just one property, something like this:

<ComplexType Name="PriorityWrapper" >
          <Property Type="Int32" Name="Value" Nullable="false" />
</ComplexType>

Then if you want to have a property in an Entity that returns an Enum instead use the wrapping ComplexType.

As I said this only works in POCO. The reason is you need to do some interesting things in your PriorityWrapper complex type class:

public class PriorityWrapper
{
   private Priority _t; 
   public int Value {
      get {
         return (int) _t;
      }
      set {
        
_t = (Priority) value;
      }
   }
   public Priority EnumValue
   {
      get {
         return _t;
      }
      set {
         _t = value;
      }
   }
}

Notice it has a Value property of type int just like the ComplexType definition, but it also has a way to set and get the Priority too via the EnumValue property.

Now we have this class we can use it in our POCO entities, so for example imagine you have a Task entity:

public class Task
{
    public virtual int Id { get; set; }
    public virtual PriorityWrapper Priority { get; set; }
    public virtual string Title{ get; set;}
}

The next step is interesting, add some implicit conversion between PriorityWrapper and Priority:

public static implicit operator PriorityWrapper(Priority p) 
{
   return new PriorityWrapper { EnumValue = p };
}

public static implicit operator Priority(PriorityWrapper pw)
{
   if (pw == null) return Priority.High;
   else return pw.EnumValue;
}

With these implicit conversions in place you gain the illusion that the Priority property on the Task class is actually a Priority.

For example you can do this:

Task task  = new Task {
   Id = 5,
   Priority = Priority.High,
   Title = “Write Tip 23”
};

Rather than this needing to do this:

Task task  = new Task {
   Id = 5,
   Priority = new PriorityWrapper {EnumValue = Priority.High },
   Title = “Write Tip 23”
};

And this:

if (task.Priority == Priority.High)

Rather than this:

if (task.Priority.EnumValue == Priority.High)

But what about queries?

You can even use this enum in queries:

var highPriority =
            from task in ctx.Task
            where task.Priority.Value == (int) Priority.High
            select task;

Cool huh?

Now this is not as good as if we natively supported enums, but it is not that far off, especially from the perspective of someone programming against your entities.

Enjoy.

This is 23rd post in my ongoing series of Entity Framework Tips.

Posted: Friday, June 05, 2009 9:12 PM by AlexJ
Filed under: , , ,

Comments

Mikael Henriksson said:

GREAT! I did the exact same thing in a billing project where the int value didn't say much of the status. Never though of doing it for the entity framework.

Nice solution, that should shut the critics up for a while :)

# June 5, 2009 6:08 PM

Ngoc said:

Will this code supported in this version of EF?

var products = from p in db.Products

              select new Product

              {

                  Id = n.Id,

                  Name = n.Name

              };

I tried it with VS2010 Beta1 and this exception still occured (same in the previous version of EF) while it is valid in LINQ To SQL:

"The entity or complex type 'Demo.Product' cannot be constructed in a LINQ to Entities query."

# June 6, 2009 2:59 AM

AlexJ said:

Ngoc,

That code isn't supported but this is:

var anonproducts =

      from p in db.Products

      select new {Id = n.Id, Name = n.Name};

var products =

      from p in anonproducts.AsEnumerable<T>()

      select new Product {Id = p.Id, Name = p.Name};

Alex

# June 6, 2009 12:09 PM

MichaelGG said:

EF doesn't support enums? What? That's... wierd. Even Linq-to-Sql does.

# June 7, 2009 11:50 PM

keith patton said:

add the support, how hard can it be!!

# June 8, 2009 4:22 AM

Phil Smith said:

is PriorityWrapper missing a constructor?

# June 8, 2009 11:45 AM

AlexJ said:

Phil,

No, I'm using the default constructor everywhere, along with a Object Initializer syntax.

I.e.

new Customer {Name = "ACME"};

rather than

new Customer("ACME");

Hope this helps

Alex

# June 8, 2009 12:23 PM

AlexJ said:

@Keith and Michael,

I hear you.

Alex

# June 8, 2009 12:24 PM

Phil Smith said:

Many thanks. Got my EF 4.0 pseudo enums working now.

Can't see why Microsoft aren't implementing Enum mapping with ability to switch map to be either the int index or the string value.

I wanted to pass the string section through to the database so my code is a little different. If anyone is interested here's my code for an organization type enum:

public enum OrganizationTypeEnum

   {

       Company,

       Subsidiary,

       Division,

       Department,

       Team

   }

   public class OrganizationTypeEnumWrapper

   {

       private OrganizationTypeEnum _organizationType;

       public OrganizationTypeEnum OrganizationType

       {

           get { return _organizationType; }

           set { _organizationType = value; }

       }

       public string StringValue

       {

           get { return _organizationType.ToString(); }

           set { _organizationType = (OrganizationTypeEnum)Enum.Parse(typeof(OrganizationTypeEnum), StringValue, true); }

       }

       public static implicit operator OrganizationTypeEnumWrapper(OrganizationTypeEnum p)

       {

           return new OrganizationTypeEnumWrapper { OrganizationType = p };

       }

       public static implicit operator OrganizationTypeEnum(OrganizationTypeEnumWrapper pw)

       {

           if (pw == null) return OrganizationTypeEnum.Company;

           else return pw._organizationType;

       }

   }

Does anyone know whether EF 4.0 supports multiple model diagrams? My database will have circa 200 tables and i do not want 1 diagram.

# June 8, 2009 2:32 PM

AlexJ said:

@Phil,

Nice solution, I was meaning to talk about that too, but it's good to see you worked it out!

As for your question about multiple diagrams. Unfortunately the answer is no, there was talk of implementing something like that, but I think it got left on the cutting room floor, so to speak.

Alex

# June 8, 2009 8:08 PM

Phil Smith said:

Any ideas why Microsoft have not implemented Enums. My guess is that because behind the scenes they are instantiated as structs derived from base System.Enum then they do not support inheritance and that the EF4.0 implementation of change tracking will therefore not work with them? (EF 4.0 creates derived proxy objects from your POCO classes). Then again I may be talking fluff?

I wonder whether its possible to somehow use extension methods on string type to perform implicit casts?

# June 9, 2009 4:24 AM

AlexJ said:

Phil,

No I don't think there are any major technical issues. We don't need to create proxies for your Enums, because they are simply a property of your POCO class.

The only reason they aren't in the product is we were focusing on other things, like POCO, Model First etc.

There is still some time to react to your and other peoples feedback so you never know.

As for you question about extension methods and implicit casts, not sure, it would be nice wouldn't it

Alex

# June 9, 2009 11:28 AM

Morten Mertner said:

It has been a source of constant annoyance with EF that it does not support enums. I could live with it being limited to only support mapping to int columns, but all we're talking about here is the ability to make a few casts. It'll take you an hour to implement but waste a million hours for all the EF users if you don't..

# June 12, 2009 5:41 PM

AlexJ said:

@Morten,

I hear you. Unfortunately no piece of code takes an hour at Microsoft. Not that I'm making excuses.

I really want to see this get in too.

Alex

# June 12, 2009 5:52 PM

Roger Hendriks said:

Hi,

Nice but too bad it's not in EF4. We even got this running in EF1 with Int and Char support. Just give it a week and make EF a real professsional ORM :)

# July 4, 2009 3:46 AM

AlexJ said:

Roger,

I hear you. I wish it was just a week. While this from Eric Lippert is tongue in cheek there is a big element of truth too:

How many Microsoft employees does it take to change a lightbulb...

http://blogs.msdn.com/ericlippert/archive/2003/10/28/53298.aspx

- Alex

# July 7, 2009 8:34 PM

MichaelGG said:

Alex -- yes, I understand how much work MS has to do to ship features. But MS is so far behind in this space in general. I was really hoping that EF4 might be something I could drop NHibernate for. But it doesn't look that way :(. It's not just this one feature, but it's that if this one little tiny thing doesn't work right, then I can only imagine there must be all sorts of things all over the place that won't work right once we get into it.

In fact, on a new project, we decided to simply not bother even looking at EF4 because of the high probability that we might run into problems at some point, even if we don't know what the problems are now. With NHibernate, it's a safe choice, and we know we're not gonna hit anything too strange that we can't quickly find a fix/workaround for.

# July 8, 2009 1:19 AM

betterBeAnnonymousThisTime said:

<i>The only reason they aren't in the product is we were focusing on other things, like POCO, Model First etc.</i>

ok, someone is either joking here or I am being a dumb EF user...

either way, why all that feedback then?

finger crossed for enums support in EF 4.0

I appreciate your work Alex, but I've dealt with other commercial ORM companies and (believe it or not) any request was made during the first 24 hours /and/or week (depending on the problem to deliver it)

I hope EF won't have a FREE (you get what you payed for) moniker, because, strangely, Microsoft is on the right way

in the history you aren't remembered by the good things you've done but by the few bad you've deployed, don't destroy its name

thanks (and I am not trolling here)

# July 14, 2009 12:53 PM

dbuckle said:

Is there a similar way of getting this working in 3.5 SP1 or is this a 4.0 only trick?

I've very new to EF and I'm trying to migrate from LINQ2SQL to EF and this is a real headache (and we can't use 2010 as its not rtm afaik)

thanks

# November 5, 2009 1:32 PM

Dave said:

You can tidy this example up with generics, a bit:

   public class EnumType<T>

   {

       private T enumType;

       public int Value

       {

           get { return (int)(object)enumType; }

           set { enumType = (T)(object)value; }

       }

       public T EnumValue

       {

           get { return enumType; }

           set { enumType = value; }

       }

       public static implicit operator EnumType<T>(T p)

       {

           return new EnumType<T>{ EnumValue = p };

       }

       public static implicit operator T(EnumType<T> pw)

       {

           if (pw == null) return default(T);

           else return pw.EnumValue;

       }

   }

   public enum wah

   {

       something,

       boo,

       legs

   }

   public class bum

   {

       public bum()

       {

           EnumType<wah> www = wah.legs;

       }

   }

# November 6, 2009 2:31 AM

AlexJ said:

@Dave,

Unfortunately I suspect your example won't work in Beta2 because of a limitation in the EF, that means properties have to be declared at the same level in the CLR type hierarchy as they do in the EDM. Which I think means that the EF won't be able to map classes derived from your Generic Base class to the corresponding EDM ComplexType.

We've made some changes post Beta2 that *might* fix the problem. Basically this sort of generic base type trick will work for Entities post Beta2, not sure about ComplexTypes though.

Alex

# November 6, 2009 7:11 AM

Steve said:

How can we make this work with the "Code Only" approach where you don't have the edmx file to define the complex type?

# November 27, 2009 7:12 AM

AlexJ said:

Well you need to register the wrapper as a ComplexType something like this:

builder.ComplexType<PriorityWrapper>()

Code-Only 'should' pick up the int / string properties but ignore the enum property. I haven't tested this yet myself, but I think it should work.

Keen to hear how you go.

Cheers

Alex

# November 27, 2009 8:42 AM

Steve said:

I tried using the ComplexType() method and it *sort of* worked.  The only way I could get it to work was if the actual database column name was in the form: <PropertyName>_Value (e.g., "Priority_Value").  I can't figure a way to make it work if I want my DB column named to be called something else.

Incidentally, I created a generic class to wrap this the same way Dave suggest above and it works fine (although suffers from the same problem where I can't control the DB column name).  

If you have any suggestions for how I can specify the DB column name, I'd appreciate it.

Sorry the belabor the point everyone else has made on the comments above, but if Microsoft doesn't simply support enums the way LINQ to SQL has done for a number of years, it will be quite disappointing.

# November 27, 2009 12:22 PM

AlexJ said:

@Steve,

To specify the column name you need to specify a mapping explicitly.

So for example if you have this:

public class Task{

 public int ID {get;set;}

 public string Name {get;set;}

 ...

 public PriorityWrapper Priority {get;set;}

}

You would need something like this:

builder.ComplexType<PriorityWrapper>();

builder.MapSingleType<Task>(

  t => new {

     id = t.ID,

     name = t.Name,

     ...

     priority = t.Priority.Value

  }

);

As you see you simply '.' through to the property of the ComplexType you want to map.

Re: not supporting Enum's properly - I absolutely agree - it is a real shame.

Hope this helps

Alex

# November 27, 2009 5:24 PM

Steve said:

Alex - Thanks for your response.  It looks like this *sort of* worked.  A couple of interesting points that I'm observing:

- If I want to use enums than this MapSingleType() appears to be the only way to make it work with the code only approach which means I have to map out every property of my type just to get it to see the enum.  Without the enum, I can just rely on convention and save myself all of that code.  So not incredibly user friendly.

- When I switched to using this, the generic Wrapper<T> no longer worked.  I had to switch back to the non-generic wrapper which means it's not a very re-usable situation (and hence, not very developer-friendly) for enums.

- When I don't use the MapSingleType() method, I can't control the column name (unfortunate) but I'm able to use the generic Wrapper<T> which make the lack of enum support slightly more palatable.  But in the end, I can't use that column name so my only choice would be to use MapSingleType().

Again, thx for your responses.

# November 27, 2009 6:40 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

  
Enter Code Here: Required

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Page view tracker