Начиная с пятой версии в библиотеке 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.
Поскольку для рассматриваемого подхода необходима существующая база данных, то воспользуемся созданной в прошлом примере. На её основе и создадим Модель. Для этого:
- Создадим в проект Модель "ADO.NET Data Entity Model", которую назовем DemoModel.edmx;
- В открывшемся окне помощника выберем "Generate from database" и создадим Модель, выбрав в качестве базы данных созданную в прошлом примере. При этом созданную Code First служебную таблицу __MigrationHistory добавлять не будем. Возьмем только UserProfiles;
- На последнем шаге отметим "Pluralize or singularize generated object names", чтобы полученные имена классов не были во множественном числе (UserProfile вместо UserProfiles).
После создания откроем Модель в дизайнере. Необходимо определить перечисление и связать его с свойством. Для этого:
- В контекстном меню свойства Role выберем пункт "Convert to Enum...";
- В появившемся диалоге зададим:
- Имя перечисления: UserRole;
- Тип: Byte;
- Члены перечисления: Administrator = 3, Writer = 2, User = 1. Значения были выбраны для совместимости сданными из прошлого примера;
- Сохраним сделанные изменения.
Модель с перечислением готова. Остается сгенерировать контекст и код классов:
- В контекстном меню дизайнера выберем пункт "Add Code Generation Item...";
- Создадим код выбрав "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