Using exisiting enum types in Entity Framework 5

When we first showed long awaited support for enum types in the Microsoft Entity Framework June 2011 CTP (which you should not be using) we received a lot of feedback. One of the areas we got lots of comments in were restrictions around mapping O-Space (i.e. CLR) enum types to C-Space (i.e. EDM) enum types. The requirements were that the full name of the C-Space enum type must match the full name of the O-Space enum type and that enum members of both types had to be an exact match. These two rules basically prevented from using already existing enum types. While I think that in most EF scenarios people will use enum types they create rather than re-use existing enum types, there was really no reason to block scenarios where existing enum types were being used. So, we went ahead and relaxed the above rules. Firstly, when trying to match enum types we only compare type name and ignore namespaces. Secondly, members are no longer required to match exactly but any member on the EDM enum type must have a counterpart in the CLR enum type (note that this is unidirectional i.e. members from the CLR enum type don’t have to exist in the EDM enum type). The latter effectively allows an EDM enum type that has no member whatsoever to be mapped to a CLR enum type that has some members (* – disclaimer: see at the bottom). After introducing the changes using existing enum types became easy. When using CodeFirst approach the entity will just have a property of the desired enum type e.g.:

    public class MyEntity
    {
        public int Id { get; set; }
        public System.DayOfWeek Day { get; set; }
    }

That’s it – it should “just work”. But what about ModelFirst or DatabaseFirst? When you look at the “Add Enum Type” dialog:

Add Enum Type Dialog

Add Enum Type Dialog

you will see the “Reference External Type” checkbox at the bottom. If you want to use an existing enum type just check this checkbox and enter the fully qualified name of the enum type in the box below the checkbox. Remember that the name of the enum type being created (“Enum Type Name” box) must match the name of the CLR enum type. Underlying types must also match. If you now close the dialog and look at the EDMX (you need to open the .edmx file with Xml Editor) you will see the following enum type definition (I copied the namespace definition to make the xml fragment valid):

<EnumType Name="DayOfWeek" cg:ExternalTypeName="System.DayOfWeek" xmlns:cg="http://schemas.microsoft.com/ado/2006/04/codegeneration" />

The interesting fact about the definition above is that the type has no members (we don’t need them). But the {http://schemas.microsoft.com/ado/2006/04/codegeneration}ExternalTypeName attribute is even more interesting – it contains the name of the CLR type we entered when adding a new enum type to the model. As the namespace uri indicates this attribute is used by code generation. When the code is generated whenever the enum type is referenced the templates will use the value from the {http://schemas.microsoft.com/ado/2006/04/codegeneration}ExternalTypeName attribute rather than the value from the Name attribute. Here is the result:


//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace ConsoleApplication1
{
    using System;
    using System.Collections.Generic;
    
    public partial class MyEntity
    {
        public int Id { get; set; }
        public System.DayOfWeek Day { get; set; }
    }
}

To make it clear – the {http://schemas.microsoft.com/ado/2006/04/codegeneration}ExternalTypeName attribute is not used at runtime. It exists purely to support code generation. Another caveat is that the attribute works only in DbContext based templates – which are the default code generators in EF5 and Visual Studio 2012.
Using existing enum types in Entity Framework 5 should be now easy!

* – as reported by Paweł Madurski on stackoverflow there is a bug where EF still checks for enum members when using Contains with an array of enum values (or to be more precise where a query requires creating the DbConstantExpression instance an enum value). This is tracked on Entity Framework codeplex page: http://entityframework.codeplex.com/workitem/623

Advertisements

37 thoughts on “Using exisiting enum types in Entity Framework 5

  1. Dear Pawel,

    Nice one! Thanks for adding the support and for posting about this…

    I used this and it’s working great! Doing this way It allows me to continue taking advantage of a code to localize enum descriptions.

    I decorate my enums with [Display] attribute so that I can use a Resources file to get the correct translation.

    All the best and keep rocking on the Entity Framework team. Amazing framework…

    Leniel

    Like

  2. Antonis Kleanthous says:

    Normally I save the Enum definitions in Db and use templates to get them back as enums. This makes sure they are consistent across all modules using them. Is it possible to support this scenario? I mean reading the enums from a table in db?

    Like

    • moozzyk says:

      As far as I understand the database and the code are already in-sync. For the Edmx part – you would have to make sure it is up-to-date. In your template you could load your Edmx file to XDocument, update it accordingly and save. On the other hand since members of the Edmx are used only in corner cases you could just use the “collapsed” (i.e. without members) definition of the enum type in the Edmx file and it should still work.

      Like

  3. Igor says:

    Thank you for the article, it was insightful.
    Can you also use the same enum type more than once? For example I have a participant table and a participant registration table so these represent 2 separate entities in the designer and are mapped respectively to the 2 separate tables I just mentioned. They both have a byte (tinyint) field named Gender and I have an existing enumeration also called Gender. Like in your example I mapped the first entity’s property Gender without any issue BUT when I do the same for the 2nd entity the OK button is not enabled and the warning message reads “verify that the enum type name is unique” even after checking the reference external type and adding the name space qualified enumeration type.
    I have also tried this with your example using the DayOfWeek (2 separate entities that both use the same DafOfWeek enum) and found the same issue, maybe there is a setting or work around I am not aware of? It seems like this is a bug in the designer….
    Thanks for any help you can provide!

    Like

    • Igor says:

      Doe! I think I found the answer and right after I post of course:)
      I did not realize that once you specify your enum for the first time you can then select it from the Type drop down list in the Properties pane once you have the property/column selected in the designer.

      Like

      • moozzyk says:

        I am glad that you figured that out. You can have only one type (be it entity type, enum type or complex type) with the given name. Once you add it and you would like to re-use it you need to pick it from the drop down.

        Like

      • Rick says:

        Thanks for coming back to post your solution. I too had exactly the same issue as you – I couldn’t find the solution though!!! 😦 until I found your comment.

        Like

  4. Andrej says:

    Doesn’t work for me:

    Schema specified is not valid. Errors:
    No corresponding object layer type could be found for the conceptual type
    ‘EntityDataModel.EmployeeType’.

    Where ‘EntityDataModel.EmployeeType’. is from C-Space

    Like

  5. Andrej says:

    Well it turned out that the error occurs only if table-field is tinyint and enum underlying type set to Byte. The moment I changed both to int it worked. This seems like a EF bug and I’m not really keen using 4bytes instead of 1byte for each enum.

    Like

    • moozzyk says:

      This should work. In fact I just tried the following model:

      public enum TestEnum : byte {}
      public class TestEntity
      {
      public int Id { get; set; }
      public TestEnum Enum { get; set; }
      }
      public class MyContext : DbContext
      {
      public DbSet Entities { get; set; }
      }

      And I dumped the edmx using:

      EdmxWriter.WriteEdmx(
      new MyContext(),
      XmlWriter.Create(Console.Out, new XmlWriterSettings() { Indent = true }));

      And I got:
      C-Space

      <EntityType Name="TestEntity">
      <Key>
      <PropertyRef Name="Id" />
      </Key>
      <Property Name="Id" Type="Int32" Nullable="false" p4:StoreGeneratedPattern="Identity" />
      <Property Name="Enum" Type="Self.TestEnum" Nullable="false" />
      </EntityType>
      <EnumType Name="TestEnum" IsFlags="false" UnderlyingType="Byte" />

      S-Space:

      <EntityType Name="TestEntity">
      <Key>
      <PropertyRef Name="Id" />
      </Key>
      <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
      <Property Name="Enum" Type="tinyint" Nullable="false" />
      </EntityType>

      Did you set the UnderlyingType attribute of the EnumType element in your C-Space model to Byte? If the underlying type in the CSDL does not match the underlying type of the eunm type in your app the two are considered to be different types. If you did and it still does not work – can you provide me with a simplified repro – something like I did above – enum and entity type definitions and corresponding EDMX?

      Like

      • Andrej says:

        Thanks moozzyk for prompt reply. Going through your example I noticed that I didn’t explicitly identified enum type like you did:

        public enum TestEnum : byte {…}

        instead all of my enums are already defined so they look like this:

        public enum TestEnum {…}

        which I understand defaults to int but I assumed conversion from byte to int would be implicit (like it used to be in LinqToSQL). And if not shouldn’t there be a check at compile time between [External Type] {Enum} and [Underlying Type] {Byte}?

        Like

      • moozzyk says:

        Underlying types have to match. There are two reasons for this – firstly, if your enum is int based and the column of your database is tiny int what should happen if your enum has a member whose value is greater than 255 and would not fit in the column? Secondly, for POCO Entity Framework will ignore namespaces. If you have two enum types with the same name and different underlying types EF still will be able to pick the right one based on the underlying type (agreed, it’s a corner case). I know, that in majority of cases no one cares about the underlying type of an enum type in the app and everyone uses the default (i.e. int32) but if you don’t care about this in your app, why would you care about the database (hard drives are much cheaper than memory 😉 )?

        Like

      • Andrej says:

        Alright it does make sense that “Underlying types have to match” but my question again (but I’m not sure if you answered it) why there is no check at compile time that would tell me that [Underlying Type]{Byte} doesn’t match type of [External Type]?

        Like

      • moozzyk says:

        Sorry, I skipped this part. Artifacts are never checked at build time. ObjectContext has a constructor that takes an entity connection string which has encoded artifacts. You can build this connection string at runtime (using for instance EntityConnectionStringBuilder). If you are using config file you can always change the connection string that lives in this file without recompiling. As a result we cannot assume that we know your model at build time (this is similar to database – no one is checking at build time whether the database you specified in the connection string is up and running). Moreover, you could load some assemblies dynamically so again at build time you may not even be aware of some CLR types necessary to do a check against EDMX. Only after EF loads your artifacts (which is at runtime) it is able to check whether types specified in the edmx match CLR types available at runtime. Hopefully it makes sense.

        Like

      • Isaac says:

        Adding “: byte” to the enum solved my problem. Thank you.

        Like

  6. Shaul B says:

    This looks like a great idea, but it just ain’t working for me. I followed these instructions exactly, but the DayOfWeek field always gets generated as MyNamespace.DayOfWeek rather than System.DayOfWeek.
    What could I be doing wrong?

    Like

    • moozzyk says:

      Are you sure the “Reference External Type” checkbox is checked and the external type name is set correctly? Can you show how the enum type is defined in your CSDL/EDMX file?

      Like

      • Shaul B says:

        Confirmed. Copying and pasting all values below:

        My Enum Type Name is: DayOfWeek
        Underlying Type: Int32
        Field on DB is of type: int
        Reference external type: System.DayOfWeek

        Like

      • Shaul B says:

        Looks like the xml tags didn’t work out; let’s try again:

        <EnumType Name=”DayOfWeek” UnderlyingType=”Int32″ a:ExternalTypeName=”System.DayOfWeek” xmlns:a=”http://schemas.microsoft.com/ado/2006/04/codegeneration” />

        Like

      • moozzyk says:

        That looks correct. Do you have any other DayOfWeek enum type defined in the model/edmx as well? Are you using the VS2012 default DbContext code generator (T4) (this is the only one that understands ExternalTypeName annotation)?

        Like

      • Shaul B says:

        No other DayOfWeek defined. Dunno what T4 is, but if it’s the default, I guess that’s what I’m using, coz I didn’t know you could use a different code generator.

        Like

      • moozzyk says:

        Can you share your project somehow? I would take a look and try to figure out what’s going on there…

        Like

      • Shaul B says:

        Happy to, but I don’t want to post this kind of stuff in a public forum. Can you email me please? sgb at sabreton dot com. Thanks!

        Like

      • moozzyk says:

        One more thought – are you using VS2012 RTM and not RC or Beta?

        Like

      • Shaul B says:

        I’m using RC. Could that be it?

        Like

      • moozzyk says:

        Yes. The RC version used ObjecContext / EntityObject generators. If you are using EntityObject you need to have Edm attributes on all properties and types – including enum type. Otherwise the type will not be treated as a valid EF type. In addition the assembly needs to have EdmSchemaAttribute. It’s very unlikely that an external enum type will meet all these requirements (probably it would have to be an assembly from another EF project) and therefore we continue to generate enum types in this case. Post-RC the default code generator was changed to generate POCO entities and DbContext.derived context. In this case we respect external enum type annotation since there is no additional requirements to use external enum types.

        Like

  7. Mike Klaene says:

    Hi… I’m having an issue with doing this… It seems that no one else is having this issue…

    When I change a property on an entity that represents an int in the SQL Server table, to use an enum (underlying type Int32), the Model Browser takes the update and everything looks good. However, the underlying C# file that represents the entity does not get refreshed. The property still says its type int and not the new enum. Is there any extra step that I need to do after making the switch to enum in the Model Browser to get the C# entity to get re-generated?

    Like

    • moozzyk says:

      Is your edmx file in a folder? There was a bug in the VS2012 RTM which caused the code not to be re-generated after save (http://entityframework.codeplex.com/workitem/453). This was fixed in the VS 2012 Update1. What you can also do is right-click on the .tt file and select “Run custom tool” to enforce code generation for the T4 template you selected.

      Like

      • mkl says:

        Thanks! You set me off on the right path… When I went to debug my template, it gave me a FileNotFoundException. I can’t remember the selections I made when I first created the .tt file but I guess I didn’t do it right. Anyway – I generated a new .tt file that contains the full set of entities, deleted the original .tt file and now it works.

        Like

      • moozzyk says:

        Glad you were able to fix this!

        Like

      • mkl says:

        Well…. It still seems there’s an issue… Everytime I change the model, the only way I can get the code to generate is to create a new .tt file. It will pick up the changes, then I remove the old one. This is hardly the way I envisioned it working – or the way it should. I saw on the properties that there is a code generation strategy which is set to ‘None’. I set it to ‘Default’ (the only other option) but that doesn’t seem to help either. So, as far as I can tell right now, every time I want to regenerate my code, I need to generate a new .tt file. Frustrating to say the least. Thanks for you help though.

        Like

      • moozzyk says:

        In general each time you save your edmx file or build your project the code should be generated if the edmx file is valid. Have you installed VSUpdate 1? Does right clicking on the tt file and selecting “Run custom tool” make code to be generated? If it does not then you seem to have a bigger problem with your VS installation since this would mean that a general T4 template functionality built in the VS2012 does not work (you may try repairing your VS installation). You probably should not select “Default” code generation strategy – it will generate EntityObject/ObjectContext based classes instead of POCO/DbContext based classes. In addition if you have T4 templates and select “Default” code generation strategy you may end up having two sets of classes for your entity model but one set will not be used.

        Like

  8. Steve says:

    How to you reference and update this value. I created a enum in my BizEntity class Estatus {disabled = 0,enabled=1,}. I updated the model so it references this Enum. My table Role.Status is of type Dal.Estatus. But Dal.Estatus does not have any members, and trying to set Role.Status = BizEntity.Status.disabled is not allowed because the types are different. So Role.Status = BizEntity.Status.disabled does not work and Role.Status = Dal.Estatus.Status.disabled also does not work because it does not see disabled as a possible value for the dal enum.

    Like

    • moozzyk says:

      Obviously Role.Status = BizEntity.Status.disabled will not work since you cannot assign values of one type to a property of a different type. I am not sure I understand your architecture – why Dal.Estatus does not have any members, why do you try assign values of one type to a property of a different type? To me it looks like you could have just one enum type you would use as the type of both Role.Status and BizEntity.Status properties. The common enum type would have the properties you need. You would import this type to your model. Finally, make sure the type is not nested – in EF5 nested types are not supported.

      Like

  9. Conrad says:

    Thank you for the tutorial.
    Just implemeted this on EF 5, and found the FlagsAttribute on an enum works as well !

    Like

    • moozzyk says:

      I am glad you found it helpful. FlagsAttribute is interesting since it does not really affect any enum behavior but .ToString(). In other words – everything but .ToString() will work the same way regardless if the attribute is present or not so in reality it probably does not really matter whether you use it or not.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: