Bagikan melalui


Walkthrough: Enums (June CTP)

 


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.

For Enum Support in Code First see https://msdn.com/data/hh859576

For Enum Support in the EF Designer see https://msdn.com/data/jj248772


 

This post will provide an overview of working with enumerated types (“enums”) introduced in the Entity Framework June 2011 CTP. We will start from discussing the general design of the feature then I will show how to define an enumerated type and how to use it in a few most common scenarios.

 

Where to get the bits?

To be able to use any of the functionality discussed in this post you first need to download and install Entity Framework June 2011 CTP bits. For more details including links installation instructions see the Entity Framework June 2011 CTP release blog post.

 

Intro

Enumerated types are designed to be first class citizen in the Entity Framework. The ultimate goal is to enable them in most places where primitive types can be used. This is not the case in this CTP release (see Limitations in Entity Framework June 2011 CTP section) but we are working on addressing most of these limitations in post-CTP releases. In addition enumerated types are also supported by the designer, code gen and templates that ship with this CTP.

 

Design/Theory

As per MSDN enumerated types in the Common Language Runtime (CLR) are defined as “a distinct type consisting of a set of named constants”. Since enums in the Entity Framework are modeled on CLR enums this definition holds true in the EF (Entity Framework) as well. The EF enum type definitions live in conceptual layer. Similarly to CLR enums the EF enums have underlying type which is one of Edm.SByte, Edm.Byte, Edm.Int16, Edm.Int32 or Edm.Int64 with Edm.Int32 being the default underlying type if none has been specified. Enumeration types can have zero or more members. When defined, each member must have a name and can optionally have a value. If the value for the member is not specified then the value will be calculated based on the value of the previous member (by adding one) or set to 0 if there is no previous member. If the value for a given member is specified it must be in the range of its enum underlying type. It is fine to have values specified only for some members and it is fine to have multiple members with the same name value but you can’t have more than one member with the same name (case matters). There are two main takeaways from this:

  • conceptually, the way you define enums in EF is very similar to the way you define enums in C# or VB.NET which makes mapping enum types defined in your program to EF enum types easy
  • if you happen to have at least one member without specified value then the order of the members matters, otherwise it does not

The last piece of information is that enumerated types can have “IsFlags” attribute. This attribute indicates if the type can be used as set of flags and is an equivalent of [Flags] attribute you can put on an enum type in your C# or VB.NET program. At the moment this attribute is only used for code generation.  

Limitations in Entity Framework June 2011 CTP

The biggest limitations of Enums as shipped in the CTP are:

  • Properties that are of enumerated type cannot be used as keys
  • It is not possible to create EntityParameters that are enumerated types
  • System.Enum.HasFlag method is not supported in LINQ queries
  • EntityDataSource works with enums only in some scenarios
  • Properties that are of enum type cannot be used as conditions in the conceptual model (C-Side conditions)
  • It is not possible to specify enum literals as default values for properties in the conceptual model (C-Space)

Practice

Now that we know a bit about enums let’s see how they work in practice. To make things simple, in this walkthrough we will create a model that contains only one entity. The entity will have a few properties – including an enum property. In the walkthrough I will use “Model First” approach since it does not require having a database (note that there is no difference in how the tools in VS work for “Database First” approach). After creating a database we will populate it with some data and write some queries which will involve enum properties and values.

 

Preparing the model

  • Properties that are of enumerated type cannot be used as keys
  • Launch Visual Studio and create a new C# Console Application project (in VS File à New à Project or Ctrl + Shift + N) which we will call “EFEnumTest”
  • Make sure you are targeting the new version of EF. To do so, right click on your project on the Solution Explorer and select Properties.
  • Select Microsoft Entity Framework June 2011 CTP from the Target framework dropdown.

 

Figure 1. Targeting the in Entity Framework 4.2 June 2011 CTP

 

  • Press Ctrl + S to save the project. Visual Studio will ask you for permission to close and reopen the project. Click Yes
  • Add a new model to the project - right click EFEnumTest Project à Add New Item (or Ctrl + Shift + A), Select ADO.NET Data Entity Model from Visual C# Items and call it “EnumModel.edmx”. Then click Add.
  • Since we are focusing on “Model First” approach select Empty Model in the wizard and press Finish button.
  • Once the model has been created add an entity to the model. Go to Toolbox and double click Entity or right click in the designer and select Add New à Entity
  • Rename the entity to Product
  • Add properties to the Product entity:

- Id of type Int32 (should actually already be created with the entity)</![IF>

- Name of type String </![IF>

- Category of type Int32</![IF>

  • Change the type of the property to enum. The Category property is currently of Int32 type but it would make much more sense for this property to be of an enum type. To change the type of this property to enum right click on the property and select Convert to Enum option from the context menu as shown on Figure 2 (there is a small glitch in the tools at the moment that is especially visible when using “Model First” approach – the “Convert to Enum” menu option will be missing if the type of the property is not one of the valid underlying types. In “Model First” approach when you create a property it will be of String type by default and you would not be able to find the option to convert to the enum type in the menu – you would have to change the type of the property to a valid enum underlying type first – e.g. Int32 to be able to see the option).

 

Figure 2. Changing the type of a property to enum

  • Define the enum type. After selecting Conver to Enum menu option you will see a dialog that allows defining the enum type.  Let’s call the type CategoryType, use Byte as the enum underlying type and add a few members:

- Beverage

- Dairy

- Condiments

We will not use this type as flags so let’s leave the IsFlag checkbox unchecked. Figure 3 shows how the type should be defined.

 

 

Figure 3. Defining an enum type

  • Press OK button to create the type.

This caused a few interesting things to happen. First the type of the property (if the properties of the property are not displayed right click the property and select Properties) is now CategoryType. If you open the Type drop down list you will see that the newly added enum type has been added to the list (by the way, did you notice two new primitive types in the drop down? Yes, Geometry and Geography are new primitive types that ship in CTP and make writing geolocation apps much simpler). You can see that on Figure 4.

 

Figure 4. The type of the property changed to "CategoryType"

Having enum types in this dropdown allows you changing the type of any non-key (CTP limitation) scalar property in your model to this enum type just by selecting it from the drop down list. Another interesting thing is that the type also appeared in the Model Browser, EnumModel à Enum Types node. Note that Enum Types node is new in this CTP and allows defining enum types without having to touch properties – you can just right click it and select Add Enum Type option from the menu. Enum types defined this way will appear in the Type drop down list and therefore can be used for any non-key (CTP limitation) scalar property in the model.

  • Save the model.

Inspecting the model

  • In the Solution Explorer right click on the EnumModel.edmx
  • Choose Open With… option from the context menu
  • Select Xml (Text) Editor from the dialog
  • Press OK button
  • Confirm that you would like to close the designer
  • Now you should be able to see the model in the Xml form (note that you will see some blue squiggles indicating errors. This is because the database has not been yet created and there is no mappings for the Product entity – you can ignore these errors at the moment)
  • Navigate to CSDL content - we are only interested in changes to conceptual part of the edmx file as there are no changes related to enums in SSDL and C-S mapping changes – and you should see something like:

<EntityType Name="Product">

  <Key>

    <PropertyRef Name="Id" />

  </Key>

  <Property Type="Int32" Name="Id" Nullable="false" annotation:StoreGeneratedPattern="Identity" />

  <Property Type="String" Name="Name" Nullable="false" />

  <Property Type="EnumModel.CategoryType" Name="Category" Nullable="false" />

</EntityType>

<EnumType Name="CategoryType" UnderlyingType="Byte">

  <Member Name="Beverage" />

  <Member Name="Dairy" />

  <Member Name="Condiments" />

</EnumType>

What you can see in this fragment is the entity type and the enum type we created in the designer. If you look carefully at the Category property you will notice that the type of the property refers to the EnumModel.CategoryType which is our enum type.

 

Inspecting code generated for the model

1.

  • In Solution Explorer double click EnumModel.Designer.cs
  • Expand Enums region in the file that opened. The definition for our enum type looks like this:

    [EdmEnumTypeAttribute(NamespaceName="EnumModel", Name="CategoryType")]

    [DataContractAttribute()]

    public enum CategoryType : byte

    {

        /// <summary>

        /// No Metadata Documentation available.

        /// </summary>

        [EnumMemberAttribute()]

        Beverage = 0,

   

        /// <summary>

        /// No Metadata Documentation available.

        /// </summary>

        [EnumMemberAttribute()]

        Dairy = 1,

   

        /// <summary>

        /// No Metadata Documentation available.

        /// </summary>

        [EnumMemberAttribute()]

        Condiments = 2

    }

The underlying enum type is byte – as we chose when defining the type. All the members we defined are present.

  • Find the Category property on the Product entity:

        [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]

        [DataMemberAttribute()]

        public CategoryType Category

        {

            get

            {

                return _Category;

            }

            set

            {

                OnCategoryChanging(value);

                ReportPropertyChanging("Category");

                _Category = (CategoryType)StructuralObject.SetValidValue((byte)value, "Category");

                ReportPropertyChanged("Category");

                OnCategoryChanged();

            }

        }

        private CategoryType _Category;

        partial void OnCategoryChanging(CategoryType value);

        partial void OnCategoryChanged();

The code looks very similar to the code generated for primitive properties we know from the previous version of the Entity Framework. The only differences are additional casts required to make enum values work correctly.

Creating the database

  • Double click EnumModel.edmx in the Solution Explorer to open the model in the designer
  • Confirm that you want to close the model opened in the Xml editor if prompted
  • Right click in the designer
  • Select Generate Database from Model… menu option to open a wizard which will guide you through the process
  • Click New Connection… button - Since we are using Model First approach we want to create a new connection to the database.
  • Enter the name of the database server you would like to use (e.g. “.\SQLExpress” if Sql Server Express edition is installed on your box) in the dialog that opened
  • Test the connection using Test Connection button
  • Press OK button if the connection worked
  • In the wizard click Next button. You should see a SQL script that will be used to create the database
  • Click Finish button – a new window containing the script will open in Visual Studio.
  • Click the Execute SQL icon in the toolbar (or use Ctrl + Shift + E key combination) to execute the script and create the database (note that this step may require administrator rights so you may need to restart Visual Studio as administrator)

Playing with enums

So far, so good. The model has been created. It contains an enum type and an entity type with a property of the enum type. Database has been also created. We need to populate the database with some data and will be ready to query the database with queries using enum types.

  • Create a method that adds a few entities to the database. Use the following code to do that:

 

private static void Seed()

{

    using (var ctx = new EnumModelContainer())

    {

        if (!ctx.Products.Any())

        {

            ctx.AddToProducts(new Product() {

                Name = "Milk", Category = CategoryType.Dairy });

            ctx.AddToProducts(new Product() {

                Name = "Seattle's Rain", Category = CategoryType.Beverage });

            ctx.AddToProducts(new Product() {

                Name = "Cayenne Pepper", Category = CategoryType.Condiments });

            ctx.AddToProducts(new Product() {

                Name = "Sour Cream", Category = CategoryType.Dairy });

            ctx.AddToProducts(new Product() {

                Name = "Chevy Volt", Category = (CategoryType)255 });

            ctx.SaveChanges();

        }

    }

}

The method is pretty straightforward. It checks whether there are any entities in the database and if there aren’t it adds some default entities. Certainly, when creating entities we are able to use the members of the enum type we defined in the model. Interestingly however C# and VB.NET allow for kind of abusing enum types by allowing specifying a value that does not correspond to any of the specified members of the enum type (internally we call such a value “unnamed enum value/member”). This is what happens with the last entity – we did not have a category for this product so we used a number that is in range of the enum underlying type and cast it to the actual enum type

  • Create a helper method to display entities. It will just iterate over entities and print Id, Name and Category just like this:

private static void DisplayProducts(IEnumerable<Product> products)

{

    Console.WriteLine("Products");

    foreach (var p in products)

    {

        Console.WriteLine("Id: {0}, Name: {1}, Category: {2}", p.Id, p.Name, p.Category);

    }

}

  • Modify Main function so that it invokes Seed method and then displays all entities we have in the database. Here is the code:

static void Main(string[] args)

{

    Seed();

    using (var ctx = new EnumModelContainer())

    {

        DisplayProducts(ctx.Products);

    }

}

    • Ctrl + F5 to run the program and you should see the following:

 

Products
Id: 1, Name: Milk, Category: Dairy
Id: 2, Name: Seattle's Rain, Category: Beverage
Id: 3, Name: Cayenne Pepper, Category: Condiments
Id: 4, Name: Sour Cream, Category: Dairy
Id: 5, Name: Chevy Volt, Category: 255
Press any key to continue . . .

    • The results seem correct. One interesting thing to note is that for named enum members the name of the member is displayed (e.g. Dairy, Beverage etc.) while for the unnamed enum values – the entity with Id = 5 – we got the value as a number

 

So, we were able to create entities with enum properties save them to database and then read them from the database (i.e. round tripping seems to work) and show to the user. In addition we were able to use a value that did not have any corresponding enum type member and nothing blew up. Nice. But this is not actually where enums really shine. The place where enums really shine are queries. It is much more natural to say “Get me all yellow sports cars I own” than “Get me all my sports cars whose color is 6”. So let’s do some queries.

  • In the Main method instead of showing all the results use a Linq query that returns only dairy products:

 

static void Main(string[] args)

{

    Seed();

    using (var ctx = new EnumModelContainer())

    {

        var q = from p in ctx.Products

                where p.Category == CategoryType.Dairy

                select p;

        DisplayProducts(q);

    }

}

Here is the result:

 

Products
Id: 1, Name: Milk, Category: Dairy
Id: 4, Name: Sour Cream, Category: Dairy
Press any key to continue . . .

  • Now let’s order our entities by category in descending order. Replace the previous query in the Main method with:

 

var q = from p in ctx.Products

        orderby p.Category descending

        select p;

The result is:

 

Products
Id: 5, Name: Chevy Volt, Category: 255
Id: 3, Name: Cayenne Pepper, Category: Condiments
Id: 4, Name: Sour Cream, Category: Dairy
Id: 1, Name: Milk, Category: Dairy
Id: 2, Name: Seattle's Rain, Category: Beverage
Press any key to continue . . .

Using enum values in queries is very natural – queries are much easier to write and to understand. Moreover using enums does not change how queries work – we translate enum properties to corresponding database columns and enum values to corresponding values of the enum underlying type. As a result queries containing enum values or properties work the same way as queries containing only primitive properties as values – everything is translated to SQL and pushed to the database.

 

The above examples are just the most common scenarios where enum types can be used in EF. However enum types are also supported as properties on complex types, in ESQL queries, as parameters or return types of stored procedures or in TVFs (Table Valued Functions) – another new feature which ships with this CTP. Finally the CTP includes also an updated version of EF 4.1 that supports enums, so you are able to use enums with CodeFirst.  We will cover how to use Enums with Code First in an upcoming blog post.

 

Summary

In the walkthrough we went through the process of creating and using enumerated types in the Entity Framework June 2011 CTP1. First we looked at a high level overview of the design of enum types in the EF. Then we created a model with an entity that contained a property of enum type. We inspected edmx file and the generated code for changes related to enums. From the model we prepared we generated a database. Finally, we added entities with enum properties to the database and used enum values when querying the database with LINQ.

 

Support and Feedback

As always we appreciate any feedback you may have on enumerated types in the Entity Framework or Entity Framework in general.  For support please use the Entity Framework Pre-Release Forum

 

Pawel Kadluczka, Entity Framework Developer.

Comments

  • Anonymous
    July 01, 2011
    Are there any official plans to allow enumerated types by keys for future CTPs or RTW?

  • Anonymous
    July 01, 2011
    What does the underlying data structure look like? do we get a CategoryType table?

  • Anonymous
    July 02, 2011
    @Chris: yes, the current plan is to enable enums to participate in entity keys in RTM or sooner. As usual, we can't promise it will happen though.

  • Anonymous
    July 02, 2011
    @Anthony: like in .NET programming languages, EF enums always have an integer underlying type. In this release we plan to support mapping enums to integer database columns only. You can constrain the column with a check or FK in the database if you want but EF won't reason about that.

  • Anonymous
    July 04, 2011
    Any plans to support custom value mapping as in NHibernate, for example, mapping to char or varchar.

  • Anonymous
    July 04, 2011
    Any plans to support custom value mapping as in NHibernate, for example, mapping to char or varchar.

  • Anonymous
    July 04, 2011
    @Jason: this is not in the plans for this release, but we would like to add the general ability to perform transformations at the property/column mapping-level, which would enable mapping enums to string columns but also other interesting scenarios.

  • Anonymous
    July 04, 2011
    hey diego, it´s great to have enum support in ef but after installing the ctp we can never generate a database  from the model?! we have installed the 'Entity Designer Database Generation Power Pack' too. So we get the Error: Could not load file or assembly 'Microsoft.Data.Entity.Design.DatabaseGeneration, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies.

  • Anonymous
    July 05, 2011
    The comment has been removed

  • Anonymous
    July 11, 2011
    Quote: "it is fine to have multiple members with the same name but you can’t have more than one member with the same name" Is it me, or is that a weird sentence? :)

  • Anonymous
    July 18, 2011
    Just left a message, simple rookie mistake, forgot the SaveChanges on the ObjectContext.

  • Anonymous
    August 15, 2011
    Does OData support the addition of Enums?  What happens to my WCF Data Services endpoint if I use enums?

  • Anonymous
    August 17, 2011
    Can someone post sample code / database using entities that have enums? I can't for the life of me get the enums to show up in the database and I would love to compare my config files, sql connections, included references, etc.. to a working sample project.

  • Anonymous
    August 19, 2011
    @Jordan, i have the same problem too with code-first. :(

  • Anonymous
    August 22, 2011
    I am surprised no one at Ado blog has posted a working sample solution with enum support! It would take 10 minutes and really really help users debug.

  • Anonymous
    August 30, 2011
    > Finally the CTP includes also an updated version of EF 4.1 that supports enums, so you are able to >use enums with CodeFirst.  We will cover how to use Enums with Code First in an upcoming blog post. I am using Code First and really need this feature.  Is there any progress relating writing the mentioned post? I am looking forward to a post/demo from someone, who can describe how to implement Enums in Code First

  • Anonymous
    September 01, 2011
    Can we use nested enums defined elsewhere in our code?

  • Anonymous
    January 22, 2012
    Any workaround for previous versions? My situation: Silverlight RIA, I wanna be able to see the enum property only, plus LINQing against it.

  • Anonymous
    February 07, 2012
    @Stephen: OData currently does not support enums. You will get an exception if you try to use an enum property on your entity @Jordan: enums are only "CSpace feature" - it means that in the database you will have just a regular column e.g. of int type whose values are cast to the enum type you use in your C#/VB.NET program during materialization. @Jordan, Niels: A new blog post on enums will be published shortly - it should address your concerns @dj_kyron: Unfortunately nested types (including enums) are not supported by EF at the moment @Shimmy: Unfortunately there are no plans for adding support for enums to EF1 and EF4 however .NET Framwork 4.5 is an "in-place" update so EF4 will be replaced with EF vNext when you move to .NET Framework 4.5 and you will be able to take advantage of new features.

  • Anonymous
    May 01, 2012
    It's now May 1, and there still doesn't seem to be a blog post on using enums with Code First. Just putting a public automatic property of type SomeEnum on an entity doesn't seem to work. No column gets created for that property in the database table. Do I need an attribute or something? If you don't have time to write a full blog post, could you at least reply with a simple example? Thanks.

  • Anonymous
    May 01, 2012
    @Dan M: the features showcased in this CTP, including enums support, have been rolled into EF5. There is a walkthrough on how to use enums in EF5 here: msdn.microsoft.com/.../hh859576. In fact, when using Code First you should be able to add a property of an enum type and start working with it, but you will need EF5 and .NET 4.5 (currently in beta). Hope this helps.