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};AlexAnonymous
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 helpsAlexAnonymous
June 08, 2009
@Keith and Michael,I hear you.AlexAnonymous
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 removedAnonymous
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 itAlexAnonymous
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.AlexAnonymous
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 removedAnonymous
July 07, 2009
The comment has been removedAnonymous
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)thanksAnonymous
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.AlexAnonymous
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.CheersAlexAnonymous
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 helpsAlexAnonymous
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 removedAnonymous
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.ThanksAnonymous
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 conflictAnonymous
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-entitiesAnonymous
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:
and you can use it like this:public static implicit operator string(OrganizationTypeEnumWrapper pw){ return pw == null ? null : pw.StringValue;}
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.entities.Where(x => x.Status.Value == (OrganizationTypeEnumWrapper)enumValue);
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 removedAnonymous
December 28, 2010
The comment has been removedAnonymous
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?ThanksAnonymous
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{
}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?public abstract class EnumWrapper<EnumType>{ 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<LogType>{ 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 05, 2011
Oops...I posted old version. This better:public abstract class EnumWrapper<EnumType, UnderlyingType>{ 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<LogType, int>{ 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?