Freigeben über


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.

Comments

  • Anonymous
    June 05, 2009
    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 :)

  • Anonymous
    June 05, 2009
    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."

  • Anonymous
    June 06, 2009
    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

  • Anonymous
    June 07, 2009
    EF doesn't support enums? What? That's... wierd. Even Linq-to-Sql does.

  • Anonymous
    June 07, 2009
    add the support, how hard can it be!!

  • Anonymous
    June 08, 2009
    is PriorityWrapper missing a constructor?

  • Anonymous
    June 08, 2009
    Phil,No, I'm using the default constructor everywhere, along with a Object Initializer syntax.I.e.new Customer {Name = "ACME"};rather thannew Customer("ACME");Hope this helpsAlex

  • Anonymous
    June 08, 2009
    @Keith and Michael,I hear you.Alex

  • Anonymous
    June 08, 2009
    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.

  • Anonymous
    June 08, 2009
    The comment has been removed

  • Anonymous
    June 08, 2009
    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?

  • Anonymous
    June 09, 2009
    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 itAlex

  • Anonymous
    June 12, 2009
    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..

  • Anonymous
    June 12, 2009
    @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

  • Anonymous
    July 03, 2009
    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 :)

  • Anonymous
    July 07, 2009
    The comment has been removed

  • Anonymous
    July 07, 2009
    The comment has been removed

  • Anonymous
    July 14, 2009
    <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.0I 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 wayin 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 namethanks (and I am not trolling here)

  • Anonymous
    November 05, 2009
    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

  • Anonymous
    November 05, 2009
    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;       }   }

  • Anonymous
    November 05, 2009
    @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

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

  • Anonymous
    November 27, 2009
    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.CheersAlex

  • Anonymous
    November 27, 2009
    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.

  • Anonymous
    November 27, 2009
    @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 helpsAlex

  • Anonymous
    November 27, 2009
    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.

  • Anonymous
    April 26, 2010
    The comment has been removed

  • Anonymous
    April 30, 2010
    @SteveCould you paste your code first mapping for Wrapper<T>builder.ComplexType<Wrapper<wah>>()  .Property(a => a.Value);EF 4.0 release say that "The Type 'EnumType`1[wah]' is not a valid complex type. Generic types are not supported.Thanks

  • Anonymous
    May 26, 2010
    How do you write queries when storing the enum value as a string. I have tried:entities.Where(x => x.Status.Value == StatusEnum.Future.ToString());but get the following exception:LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.

  • Anonymous
    June 03, 2010
    Is this still the best approach for Enums?  I am just getting started and quickly ran into this.

  • Anonymous
    June 03, 2010
    Is this still the best approach for Enums?  I am just getting started and quickly ran into this.

  • Anonymous
    June 09, 2010
    @DanielYou need to get the string before you use it:string enumValue = StatusEnum.Future.ToString();entities.Where(x => x.Status.Value == enumValue);@ScottNot sure if this is the 'Best' approach - all approaches have tradeoffs - it is however one we know works.

  • Anonymous
    July 01, 2010
    I've been trying to implementing this but am not sure where I create the public class PriorityWrapper as the codegen already creates a partial one and therefore I get an abiguity conflict

  • Anonymous
    July 14, 2010
    Please make this work. Its absolutly a must.I like what i am seeing with the code first and convention based mapping/configuration just like Fluent NHibernate, but i wont change if something as simple as mapping to an enum doesnt work.

  • Anonymous
    July 18, 2010
    What modifications are needed to make this work in the final release (as the post was written for beta)? PriorityWrapper must be in a different namespace since it is not marked as partial.Is that supposed to be the case? If so , the assigning and linq statements will not work as posted.If PriorityWrapper is supposed to be in the same namespace, when it is marked as partial the Value property is defined in two places.I feel I'm missing something very important or this example does not apply in its current from to the current EF release.Any thoughts?

  • Anonymous
    July 27, 2010
    Please vote: connect.microsoft.com/.../support-enums-as-property-types-on-entities

  • Anonymous
    July 28, 2010
    @Anthony MainThis sample is for POCO only. So, default code generator must be turned off.

  • Anonymous
    July 28, 2010
    @Anthony MainThis sample is for POCO only. So, default code generation must be turned off.

  • Anonymous
    September 08, 2010
    This approach doesn't work for self-tracking entities as well.

  • Anonymous
    September 13, 2010
    @Dennis and author,Yes, sample is for POCO, but it doesn't seem to work to well with POCO Entity templates.  My problem, the complex type the author says to create is generated by the T4 templates, especially the property ("Value"), in which we need to add implementation too (per author).  Thus, how do we add our special wrapper implementation for our complex type, when the POCO Entity T4 templates will overwrite it ?Do we have to define the complex type by using 'code-first', b/c I am missing something trying 'design-first' with T4 POCO Entity templates ?Please help/

  • Anonymous
    December 09, 2010
    @Daniel, @AlexJYou don't need to get the string before you use it. Just add third implicit conversion - to string, like this:

    public static implicit operator string(OrganizationTypeEnumWrapper pw){    return pw == null ? null : pw.StringValue;}
    and you can use it like this:
    entities.Where(x =&gt; x.Status.Value == (OrganizationTypeEnumWrapper)enumValue);
    It works by immediately converting enumValue to OrganizationTypeEnumWrapper, and later, where strings are to be compared, converts it to string by the implicit operator newly added.The minus of this is casting, but it's like casting to (int) in original solution, and far way better than getting string before creating query.

  • Anonymous
    December 22, 2010
    A simpler version:public class Participant{   public int ParticipantId { get; set; }   public string FirstName { get; set; }   public string LastName { get; set; }   public string Username { get; set; }   [Column(Name="Gender")]   public int InternalGender { get; set; }   [NotMapped]   public Gender Gender   {       get { return (Gender)this.InternalGender; }       set { this.InternalGender = (int)value; }   }   public DateTime DateOfBirth { get; set; }}

  • Anonymous
    December 28, 2010
    The comment has been removed

  • Anonymous
    December 28, 2010
    The comment has been removed

  • Anonymous
    January 13, 2011
    @DarrenI couldn't get your example to work.The property  ' public Gender Gender' won't compile...Got any suggestions?

  • Anonymous
    February 17, 2011
    Hi Alex,As of today, is Enum supported in EF for .NET 4(I mean VS2010)? I could'nt find clear documentation for this. Can you help with a small example in case it's available?Thanks

  • Anonymous
    March 09, 2011
    Is there an example project I can download from you Alex? I've been trying hard to get this thing to work and feel rather stupid in the process. I can't get it to work with WCF. When I set my wrapper object to the enum value it says I can't implicitly convert. Here is my wrapper:   public partial class AddressTypeWrapper   {       #region Primitive Properties       private AddressType _a;       public int Value       {           get           {               return (int)_a;           }           set           {               _a = (AddressType)value;           }       }       public AddressType EnumValue       {           get           {               return _a;           }           set           {               _a = value;           }       }       public static implicit operator AddressTypeWrapper(AddressType addressType)       {           return new AddressTypeWrapper { EnumValue = addressType };       }       public static implicit operator AddressType(AddressTypeWrapper addressTypeWrapper)       {           return addressTypeWrapper.EnumValue;       }       #endregion   }I changed my entity property to be the wrapper type:    [DataMember]       public AddressTypeWrapper AddressTypeID       {      get;    set;         }

  • Anonymous
    May 04, 2011
    The above is good, but it could be cleaned up a bit with generics and a little black magic, if you want to reuse the Value and EnumValue properties (can't do anything with the conversion operators).  And maybe someone could suggest better black magic for my EnumValue property?namespace DataTest.Model{

    public abstract class EnumWrapper&lt;EnumType&gt;{    public int Value { get; set; }    public EnumType EnumValue    {        get { return (EnumType) Enum.ToObject(typeof(EnumType), this.Value); }        set { this.Value = (int) Enum.Parse(typeof(EnumType), Enum.GetName(typeof(EnumType), value)); }    }}public class LogTypeWrapper : EnumWrapper&lt;LogType&gt;{    public static implicit operator LogTypeWrapper(LogType enumValue)    {        return new LogTypeWrapper { EnumValue = enumValue };    }    public static implicit operator LogType(LogTypeWrapper wrapper)    {        return (wrapper == null) ? default(LogType) : wrapper.EnumValue;    }}
    }By the way, Mr. Microsoft, we sure would like Enum type parameter constraints (seriously, why can't I do this:[where EnumType : Enum]?  Why is that explicitly forbidden?

  • Anonymous
    May 05, 2011
    Oops...I posted old version. This better:

    public abstract class EnumWrapper&lt;EnumType, UnderlyingType&gt;{    public UnderlyingType Value { get; set; }    public EnumType EnumValue    {        get { return (EnumType) Enum.ToObject(typeof(EnumType), this.Value); }        set { this.Value = (UnderlyingType) Enum.Parse(typeof(EnumType), Enum.GetName(typeof(EnumType), value)); }    }}public class LogTypeWrapper : EnumWrapper&lt;LogType, int&gt;{    public static implicit operator LogTypeWrapper(LogType enumValue)    {        return new LogTypeWrapper { EnumValue = enumValue };    }    public static implicit operator LogType(LogTypeWrapper wrapper)    {        return (wrapper == null) ? default(LogType) : wrapper.EnumValue;    }}

  • Anonymous
    May 11, 2011
    All this code to make EF support enums ? No, thanks.

  • Anonymous
    January 03, 2012
    Is there a way of enum support via entity designer (db first) via generated DbContext entities?