Andrey on .NET | Использование перечислений в Entity Framework 5.0

Использование перечислений в Entity Framework 5.0

Начиная с пятой версии в библиотеке Entity Framework появилась поддержка перечислений. Давайте рассмотрим её использование на практике.

Ограничения

Прежде всего необходимо отметить, что поддержка enum заявлена только для проектов под .NET 4.5. Для предыдущих версий платформы она отсутствует и соответствующие свойства просто не будут отражены в таблицах базы данных.

Поэтому для дальнейшей демонстрации создадим пустое решение (Blank solution) под названием EF5EnumDemo. Все включаемые в него проекты будет нацелены на .NET 4.5.

Использование перечислений в Entity Framework Code First

Добавим консольный проект EF5EnumCodeFirstDemo для демонстрации работы enum при использовании подхода Code First. В качестве целевой платформы выберем .NET 4.5 и затем добавим Entity Framework 5.0. Для этого в консоли NuGet выполним следующую команду:

PM> Install-Package EntityFramework –ProjectName EF5EnumCodeFirstDemo –Pre

Ключ –Pre используется для установки предварительной версии, т.к. на момент написания этой статьи доступна только Release Candidate версия.

В качестве базы данных будет использоваться вариант по умолчанию. В каждом конкретном случае это может быть один из двух вариантов:

  • SQL Express (если он установлен и запущен на момент установки Entity Framework)
  • или LocalDb, входящая в поставку Visual Studio 11.

Entity Framework самостоятельно определяет какой вариант доступен и создает соответствующую строку подключения по умолчанию.

Перейдем к коду. Все случае с Code First все очень просто. Достаточно создать Модель, которая содержит свойства, являющиеся перечислениями. Entity Framework легко отобразит их в соответствующей таблице. Добавим следующие классы:

  • UserRole – перечисление, указывающие роль пользователя в приложении:
namespace EF5EnumCodeFirstDemo
{
    public enum UserRole
    {
        Adminsitrator,
        User
    }
}
  • UserProfile – профиль пользователя, Модель:
namespace EF5EnumCodeFirstDemo
{
    public class UserProfile
    {
        public int Id { get; set; }

        public string Login { get; set; }

        public UserRole Role { get; set; }
    }
}
  • AppContext – контекст Entity Framework для работы с базой данных:
namespace EF5EnumCodeFirstDemo
{
    using System.Data.Entity;

    public class AppContext : DbContext
    {
        public DbSet<UserProfile> Profiles { get; set; }
    }
}

И, наконец, добавим код создания записи и её чтения в метод Main():

namespace EF5EnumCodeFirstDemo
{
    using System;
    using System.Linq;

    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new AppContext()) {
                if (!context.Profiles.Any())
                    AddUserProfile(context);

                UserProfile profile = context.Profiles.FirstOrDefault();

                Console.WriteLine("Login: {0}. Role: {1}", profile.Login, profile.Role);
            }

            Console.WriteLine("Press any key ...");
            Console.ReadKey(true);
        }

        private static void AddUserProfile(AppContext context)
        {
            context.Profiles.Add(new UserProfile() {
                Login = "User",
                Role = UserRole.User
            });

            context.SaveChanges();
        }
    }
}

Все готово к проверке. Запустим проект и в качестве результата получим строку "Login: User. Role: User". Теперь посмотрим на созданную в базе данных таблицу:

[Id] [int] IDENTITY(1,1) NOT NULL,
[Login] [nvarchar](max) NULL,
[Role] [int] NOT NULL

Как видите, перечисление успешно было отражено в таблице в поле типа int.

Изменяем тип поля в таблице

Первое что бросятся в глаза – для данного случая хватило бы byte. Ведь навряд ли будет больше 255 ролей пользователей. Для замены типа просто укажем нужный вариант в самом перечислении:

namespace EF5EnumCodeFirstDemo
{
    public enum UserRole : byte
    {
        Adminsitrator,
        User
    }
}

Теперь удалим созданную ранее базу данных, запустим проект заново и посмотрим на таблицу:

[Id] [int] IDENTITY(1,1) NOT NULL,
[Login] [nvarchar](max) NULL,
[Role] [tinyint] NOT NULL

Цель достигнута.

Модифицируем перечисление

А что будет, если изменится само перечисление. Например, добавим еще одну роль для пользователя:

namespace EF5EnumCodeFirstDemo
{
    public enum UserRole : byte
    {
        Adminsitrator,
        Writer,
        User
    }
}

Если теперь запустить приложение, то после чтения записи свойство Role будет равно Writer, а не User. Причина этого заключена в том, что в таблице сохраняется числовое представление перечисления. И если раньше 1 соответствовала значению User, то теперь это Writer.

Избежать этой ситуации можно изначально вручную задав значения для перечисления:

namespace EF5EnumCodeFirstDemo
{
    public enum UserRole : byte
    {
        Adminsitrator = 1,
        Writer = 2,
        User = 3
    }
}

Но поскольку данные уже есть в таблице, то для совместимости с ними необходимо указать для User значение 1. Тогда UserRole будет выглядеть так:

namespace EF5EnumCodeFirstDemo
{
    public enum UserRole : byte
    {
        Adminsitrator = 3,
        Writer = 2,
        User = 1
    }
}

 

Использование перечислений в Entity Framework Database First

Перейдем к использованию перечислений совместно с Database First. Добавим в решение еще один консольный проект – EF5EnumDatabaseFirstDemo. Также установим .NET 4.5 в качестве целевой платформы и добавим библиотеку Entity Framework.

Поскольку для рассматриваемого подхода необходима существующая база данных, то воспользуемся созданной в прошлом примере. На её основе и создадим Модель. Для этого:

  1. Создадим в проект Модель "ADO.NET Data Entity Model", которую назовем DemoModel.edmx;
  2. В открывшемся окне помощника выберем "Generate from database" и создадим Модель, выбрав в качестве базы данных созданную в прошлом примере. При этом созданную Code First служебную таблицу __MigrationHistory добавлять не будем. Возьмем только UserProfiles;
  3. На последнем шаге отметим "Pluralize or singularize generated object names", чтобы полученные имена классов не были во множественном числе (UserProfile вместо UserProfiles).

После создания откроем Модель в дизайнере. Необходимо определить перечисление и связать его с свойством. Для этого:

  1. В контекстном меню свойства Role выберем пункт "Convert to Enum...";
  2. В появившемся диалоге зададим:
    • Имя перечисления: UserRole;
    • Тип: Byte;
    • Члены перечисления: Administrator = 3, Writer = 2, User = 1. Значения были выбраны для совместимости сданными из прошлого примера;
  3. Сохраним сделанные изменения.

Модель с перечислением готова. Остается сгенерировать контекст и код классов:

  1. В контекстном меню дизайнера выберем пункт "Add Code Generation Item...";
  2. Создадим код выбрав "5.x DbContext Generator" и указав имя DemoModel.tt. Если этого шаблона нет в "Visual C# Items > Code", то необходимо загрузить его через раздел "Online".

Если, после завершения обработки шаблонов, открыть созданный код, то можно увидеть перечисление:

public enum UserRole : byte
{
    Administrator = 3,
    Writer = 2,
    User = 1
}

.........

public partial class UserProfile
{
    public int Id { get; set; }
    public string Login { get; set; }
    public UserRole Role { get; set; }
}

Для примера, получим все записи, у которых Role равно User:

namespace EF5EnumDatabaseFirstDemo
{
    using System;
    using System.Linq;

    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new Entities()) {
                UserProfile profile = context.UserProfiles
                    .FirstOrDefault(p => p.Role == UserRole.User);

                Console.WriteLine("Login: {0}. Role: {1}", profile.Login, profile.Role);
            }

            Console.WriteLine("Press any key ...");
            Console.ReadKey(true);
        }
    }
}

Использование перечислений в Entity Framework Model First

В данном случае отдельный пример навряд ли потребуется. Дело в том, что разница с Database First будет только в специфических шагах самого подхода. Что же касается поддержки перечислений, то после создания Модели, необходимые действия будут полностью аналогичными: конвертация свойства в enum и определение его членов.

Использование перечислений в запросах

В завершении также хотелось бы отметить, что значения перечислений могут использоваться в запросах аналогично другим типам данных. Например:

var profiles = from p in context.UserProfiles
        where p.Role == UserRole.Administrator
        orderby p.Role descending, p.Id
        select p;

Исходный код проекта (C#, Visual Studio 11, LocalDb): EF5EnumDemo.zip

Комментарии (15) -

Коротко и ясно.
Спасибо!

Добрый день, Андрей. У меня возник один вопрос касаемо Code Frist и клиент серверного взаимодействия. Пусть есть такие классы на сервере:


    public class Category
    {
        public int ID { get; set; }

        public string Name { get; set; }
    }

    public class Post
    {
        public int ID { get; set; }

        public string Content { get; set; }

        public virtual Category Category { get; set; }
    }


Клиент получает список существующих категорий, создает новый экземпляр класса Post, выбирает одну из переданных категорий и задает ее вновь созданному объекту, пересылает все это на сервер. При попытке сервера сохранить этот объект:

     db.Posts.Add(new_post);
     db.SaveChanges();

EF создает новую запись в таблице постов и при этом новую запись в таблице категорий. Вопрос такой как сделать так чтобы новая запись в таблице категорий не создавалась, а прикручивалась именно та, которая была задана клиентом. Такое поведение не понятно, хотя бы потому, что переданной в посте категории явно задан id....

@ Serg: А создаваемая категория получает новый Id?

@ Andrey: да, категория которая создается на сервере, при сохранении new_post, автоматически получает новый id, отличный от переданного клиентом

Andrey Dyachenko 20.05.2012 21:36:57

eto proishodit iz za togo chto obect Category ne nahoditsya v datacontext - sootvetstvenno EF ne znaet chto eta categoriya sushestvuet
varianta dva
1. Dobavit v Post pole CategoryId i ne zapolnyat pole Category
2. Prisoedenit Category v datacontext i ukasat status EntityState.Unchanged
blogs.msdn.com/.../...ttach-and-entity-states.aspx

Спасибо, Андрей.

@ Serg: Пожалуйста.

Будет очень неплохо иметь такой наборчик для работы. Статейка в самый раз.

Андрей 16.08.2012 19:55:43

Спасибо за статью. А какое ограничение накладывается на базу саму. Будет ли работать этот метод со взрослой базой а не только с експресс вариантом? И какова причина того, что может не создаватсья поле перечислений. Ошибок не видно, создаются ресколько полей стандартных типов а перечисление - нет. База 2008 Sr2 експресс.
Спасибо.

Будет работать как с Express, так и со стандартным MSSQL. Необходимо только EF5 + .NET 4.5.

Обратите внимание, что под .NET4 вместо EF5 ставится просто обновленная EF4. В ней нет поддержки перечислений. Скорее всего поэтому у вас и не создается поле для данного свойства.

Андрей 20.08.2012 18:16:09

Огромное спасибо за ответы. Про енум - я создал консольное приложение в 2012 студии и добавил EF5.0. Н ов базе не вижу созданный энум хоть треснись. Ну да ладно, разберемся. Подскажите что почитать пожалуйста про свойства навигации. Не понимаю его применения. Просто не понимаю. Если можно - подробнее или серию статей для начинающих.
Еще раз спасибо.

@ Андрей: Проверьте на какую версию .NET нацелен ваш проект. Должно быть 4.5. Причем если у вас сейчас 4.0 и вы смените версию, то EF5 надо удалить и добавить.

Вот тут есть немного про навигационные свойства
andrey.moveax.ru/.../

Если очень кротко, то они позволяют легко получить данные, связанные с текущей записью.

Андрей 21.08.2012 18:33:11

Спасибо. Вашт статьи я читаю и перечитываю бо пока тяжело входит в голову все это. Эту я читал.
Спасибо.

Печальный факт в том, что если у тебя уже проект на 4.0 и EF5.0.0 , то для переключения на dotnet 4.5 придётся выполнить (если там автор не врёт) удивительные па: social.msdn.microsoft.com/.../6a6da35f-00c3-4cba-8052-9973d13dea08

@ Blush:
Все из-за того, что у EF сборки для .NET4 и .NET4.5 разные.

Но все решается одной командой NuGet, которую надо выполнить в консоли после смены платформы:
update-package EntityFramework -reinstall

Она удалит и заново установит в проект EF, обновив reference на нужную сборку.

Добавить комментарий