Доступ к данным

Перенос существующих проектов под EF 5

Джули Лерман

В этой статье для получения экранных снимков использовалась RC-версия Visual Studio 2012.

 

Julie LermanВ Microsoft .NET Framework 4.5 внесен ряд изменений и усовершенствований в базовые Entity Framework API. Наиболее заметное из них — новый способ автоматического кеширования в Entity Framework ваших запросов LINQ to Entities, исключающий издержки трансляции запроса в SQL-код при повторном использовании. Эта функциональность называется Auto-Compiled Queries (автоматически компилируемые запросы), и более подробную информацию о ней и других способах повышения производительности можно прочесть в статье «Sneak Preview: Entity Framework 5 Performance Improvements» в блоге группы EF по ссылке bit.ly/zlx21L. Приятный бонус этой функциональности в том, что она контролируется Entity Framework API в рамках .NET Framework 4.5, поэтому даже приложения для .NET 4, использующие Entity Framework, получат «бесплатный» выигрыш при запуске в системах с установленной .NET 4.5.

Другие полезные новые средства, встроенные в базовый API, требуют кодирования с вашей стороны, в частности для использования перечислений, типов пространственных данных и функций, возвращающих табличные значения (table-valued functions). В дизайнере Entity Data Model (EDM) в Visual Studio 2012 тоже появились некоторые новые средства, в том числе возможность создания различных представлений модели.

В настоящее время я пишу код, относящийся к EF, по большей части с применением DbContext API, который предоставляется (как и средства Code First) отдельно от .NET Framework. Благодаря этим средствам Microsoft дает нам возможность более гибко расширять Entity Framework — они содержатся в одной библиотеке, EntityFramework.dll, которую можно установить в ваши проекты через NuGet.

Чтобы использовать преимущества поддержки перечислений и других средств, добавленных в EF в .NET Framework 4.5, вам потребуется совместимая версия EntityFramework.dll — для EF 5. Первый выпуск этого пакета имеет номер версии 5.

У меня довольно много приложений, использующих EF 4.3.1. Эта версия включает поддержку миграции, введенную в EF 4.3, плюс несколько мелких усовершенствований, добавленных чуть позже. В данной статье я покажу, как перенести на EF 5 приложение, использующее EF 4.3.1, чтобы задействовать новую поддержку перечислений в .NET 4.5. Описываемые этапы также относятся к проектам, которые на данный момент используют EF 4.1, 4.2 или 4.3.

Я начну с простого демонстрационного решения, в котором содержатся проекты DomainClasses, DataLayer и консольного приложения (рис. 1).

Существующее решение, использующее EF 4.3.1
Рис. 1. Существующее решение, использующее EF 4.3.1

Это решение было создано в Visual Studio 2010 с применением .NET Framework 4 и сборки EntityFramework.dll для версии EF 4.3.1.

В проекте DomainClasses содержатся два класса, помещенных в один файл (рис. 2), и они используют популярную в примерах кода тему Twitter. Имена этих классов: Tweeter и Tweet.

Рис. 2. Исходные классы предметной области

using System.ComponentModel.DataAnnotations;
namespace DataPointsDemo.DomainClasses
{
  public class Tweeter
  {
    public Tweeter()
    {
      Tweets = new List<Tweet>();
    }
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [MaxLength(10),Column("ExperienceCode")]
    public string Experience { get; set; }
    [MaxLength(30), MinLength(5)]
    public string UserName { get; set; }
    [RegularExpression(@"(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})")]
    public string Email { get; set; }
    public string Bio { get; set; }
    public DateTime CreateDate { get; set; }
    public byte[] Avatar { get; set; }
    public ICollection<Tweet> Tweets { get; set; }
    public string AliasPlusName
    { get { return Name + "(" + UserName + ")"; } }
  }
  public class Tweet
  {
    public int Id { get; set; }
    public DateTime CreateDate { get; set; }
    public string Content { get; set; }
    [Range(1, 5),Column("RatingCode")]
    public int Rating { get; set; }
    public Tweeter Alias { get; set; }
    public int AliasId { get; set; }
  }
}

В этом проекте Data Annotations используются не только для добавления проверок (например, RegularExpression), но и для определения некоторых элементов конфигурации: MaxLength, MinLength и Column. Последний из перечисленных, Column, указывает имя столбца в таблице (базы данных), с которой сопоставлены поля Experience и Rating.

Все три проекта ссылаются на EntityFramework.dll (версии 4.3.1). Как правило, я отделяю EntityFramework.dll и любую логику базы данных от своих классов предметной области, но в примере я решила включить их в целях демонстрации. Атрибуты MaxLength, MinLength и Column находятся в том же пространстве имен, что и проверки (System.ComponentModel.DataAnnotations), но являются частью сборки EntityFramework.

Также примечателен тот факт, что в моих классах предметной области есть два свойства, которые так и напрашиваются на использование перечислений: Tweeter.Experience (строковое) и Tweet.Rating (числовое). Ответственность за то, чтобы пользователям были доступны должные их значения, возлагается на разработчика, кодирующего с применением этих классов. Почему в них нет перечислений? Потому что базовый Entity Framework API в .NET Framework 4 не поддерживает перечисления. Но теперь в .NET Framework 4.5 (и в Code First из EF 5) их можно использовать. Поэтому давайте обновим это решение.

Хотя я открыла решение в Visual Studio 2012 RC, оно все еще ориентировано на .NET 4. Первым делом я должна переориентировать три своих проекта на .NET 4.5, что можно сделать в окне Properties каждого проекта (рис. 3). Это приходится делать последовательно для каждого проекта, поэтому, если у вас много проектов, вы, вероятно, предпочтете написать подходящий скрипт.

Смена платформы проекта с .NET Framework 4 на .NET Framework 4.5
Рис. 3. Смена платформы проекта с .NET Framework 4 на .NET Framework 4.5

Этот шаг важно сделать перед обновлением до EF 5. Я узнала об этом на собственном горьком опыте и вскоре поясню, в чем здесь дело.

После переориентации проектов на .NET Framework 4.5 вы можете выполнить обновление до EF 5. Поскольку эту сборку использует несколько проектов, вы предпочтете управлять пакетами для всего решения, а не обновлять по одному каждый проект. Команда Manage NuGet Packages доступна в контекстном меню решения в Solution Explorer. Она открывает UI диспетчера пакетов. В его левой секции выберите Updates. В средней секции, если у вас текущая версия диспетчера пакетов, вы увидите раскрывающийся список с двумя элементами: Stable Only и Include Prerelease. Если вы, как и я, делаете это до официального выпуска .NET 4.5 и EF 5, выберите Include Prerelease. В моем решении EntityFramework — единственный пакет, который нужно обновить, как показано на рис. 4. Если же вы предпочитаете работать в консоли диспетчера пакетов, то можете ввести «Install-Package EntityFramework –prerelease», но тогда вам придется делать это для каждого проекта по отдельности.

Поиск обновления до предварительной версии EntityFramework 5
Рис. 4. Поиск обновления до предварительной версии EntityFramework 5

Как только вы инициируете обновление пакета, NuGet запросит у вас, какие проекты следует обновить. Хотя все три моих проекта используют Entity Framework 4.3.1, я намерена обновить только ConsoleApplication и DataLayer, поэтому снимаю флажок с DomainClasses. Вы можете наблюдать в окне состояния, какие операции выполняются в процессе обновления. Когда обновление завершится, просто закройте диспетчер пакетов.

Один пакет, две DLL

Обновление до EF 5 повлияло на два проекта. Прежде всего версия 4.3.1 сборки EntityFramework.dll была заменена на версию 5. Вы должны проверить это во всех обновляемых проектах. Это демонстрирует, почему важно переключиться на .NET Framework 4.5 до обновления пакета. Пакет EF 5 содержит две DLL. Одна — версии 5; в ней содержится вся функциональность DbContext API и Code First, и она совместима с .NET 4.5. Другой файл имеет версию 4.4. Он остается совместимым с .NET 4. Включая эту DLL в пакет, группа EF избежала необходимости поддерживать два разных NuGet-пакета. После выпуска EF 5 вы установите тот же пакет EF 5, когда вам потребуется поддержка DbContext или Code First. Пакет обеспечит установку правильной версии в ваш проект — под .NET 4 или .NET 4.5.

В первый раз, когда я обновляла EF, я не обновила предварительно свои проекты до .NET 4.5. Мне не удалось заставить работать новый функционал, и я даже растерялась. Потом я заметила, что версия EntityFramework.dll оказалась 4.4, что еще больше удивило меня. В конце концов, я просмотрела файлы пакета в решении, увидела, что у меня два пакета, и поняла свою ошибку.

При обновлении EF 5 также изменяется файл app.config в консольном проекте и создается файл app.config в проекте DataLayer. Поскольку в исходном решении использовалось поведение Code First по умолчанию (автоматическое обнаружение релевантной базы данных), у меня не было в конфигурационном файле ни строки подключения, ни информации фабрики соединений (connection factory information). При установке EF 5 в раздел <entityFramework> этого файла был добавлен следующий подраздел:

<defaultConnectionFactory
  type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
  <parameters>
    <parameter
       value="Data Source=(localdb)\v11; Integrated Security=True;
              MultipleActiveResultSets=True" />
  </parameters>
</defaultConnectionFactory>

Кроме того, в app.config была обновлена ссылка на сборку EF, чтобы отразить номер новой версии.

В проектах, в которых нет конфигурационного файла, появляется новый app.config с конфигурацией EF 5 по умолчанию. Вот откуда взялся app.config в проекте DataLayer после обновления. Но мне не нужен конфигурационный файл в этом проекте, поэтому я просто удалю его.

А что с проектом DomainClasses?

При обновлении я пропустила проект с классами предметной области. Единственная причина, по которой мне понадобилась EntityFramework.dll в предыдущей версии решения, заключалась в том, что мне нужен доступ к Data Annotations, специфичным для EF. Теперь они перенесены в сборку .NET Framework 4.5, System.ComponentModel.Data¬Annotations.dll, и объединены с другими аннотациями данных. Таким образом, мне больше не требуется ссылаться на EF из этого проекта. Фактически теперь я могу удалить из него ссылку на EntityFramework. Вместо использования UI диспетчера пакетов я предпочла открыть консольное окно и ввела команду «uninstall-package entityframework», чтобы удалить этот пакет из данного проекта.

Но нужно сделать еще один шаг. Компилятор выдает предупреждение для файла с классами по поводу трех аннотаций данных. Изначально они были в пространстве имен System.ComponentModel.Data¬Annotations как части EntityFramework.dll. Но в сборке .NET, где они теперь находятся, они переместились в подпространство имен. Поэтому я должна добавить еще одно выражение using в начало файла с классами:

using System.ComponentModel.DataAnnotations.Schema;

Тогда компилятор будет счастлив, а значит, буду счастлива и я, поскольку тем самым я избавилась от зависимости от Entity Framework в классах, которые никак не связаны с доступом к данным. Вообще говоря, у меня сохраняется личная неприязнь к размещению в классах предметной области атрибутов, определяющих схему базы данных, и обычно я предпочитаю для этих задач конфигурации с адаптивным (текучим) API в Entity Framework. Но в небольших проектах я считаю аннотации данных вполне удобными и простыми в использовании.

Скрещивание версий

Группа EF учла вероятность установки EF 4.3.x в проект, ориентированный на .NET Framework 4.5. Если вы так и сделаете (случайно или намеренно), в IDE будет отображен текстовый файл, в котором перечисляются известные проблемы с использованием EF 4.x в проектах под .NET 4.5 и рекомендуется установить EF 5. Как только появится стабильная версия EF 5 и она станет пакетом по умолчанию, вероятность подобной ошибки со стороны разработчиков должна исчезнуть.

Переключение на перечисления

Теперь, когда все расставлено по своим местам, можно модифицировать классы предметной области, чтобы избавиться от громоздкого обходного решения и задействовать перечисления для свойств Rating и Experience. Ниже показаны два новых перечисления, и обратите внимание, что я по-разному указала значения для них, чтобы вы могли увидеть, как EF обрабатывает обе ситуации:

public enum TweetRating
{
  Suxorz = 0,
  WorksForMe = 1,
  WatchOutAPlusK = 2
}
public enum TwitterExperience
{
  Newbie, BeenAround, Ninja
}

Далее можно изменить свойства так:

[Column("ExperienceCode")]
public TwitterExperience Experience { get; set; }
[Column("RatingCode")]
public TweetRating Rating { get; set; }

Заметьте, что мне больше не нужны атрибуты для задания диапазона или длины строки в свойстве. Но будьте внимательны: я вношу это изменение без учета возможно существующих данных в моей демонстрационной базе данных. Если вы будете вводить подобные изменения в производственное приложение, то сначала вам потребуется подготовиться к изменениям в данных. Я полностью меняю смысл Experience в базе данных и, кроме того, произвольно модифицирую диапазон рейтингов твитов от 1–5 до 0–2.

После применения средств миграции Code First для обновления базы данных мы заменяем тип столбца Tweeter.ExperienceCode с nvarchar на int. Как C#, так и Visual Basic будут интерпретировать enum как целочисленный тип по умолчанию и начинать перечисление с 0. Поэтому Code First будет сопоставлять значения перечисления с типом данных int в базе данных. Вы можете указать другой тип для enum (в рамках перечислений, поддерживаемых C# и Visual Basic), и Code First будет использовать его. Например, определение enum как типа long приведет к тому, что свойства будут сопоставлены с типом данных bigint. Но по умолчанию вы всегда получите целочисленный тип с отсчетом от 0. В моем примере (в базе данных) Newbie будет представлен 0, BeenAround — 1 и Ninja — 2. Если вы полагаете, что в будущем не исключено удаление какого-либо из членов перечисления, переупорядочение или добавление новых, причем не в конец списка, тогда присваивайте им значения явным образом, как это сделала я в перечислении TweetRating. Это упрощает изменение перечисления без риска случайной модификации значений. Не забывайте, что база данных будет хранить только числовое значение, поэтому, если вы в конечном счете все же измените значение в перечислении, это поменяет смысл ваших данных, а это почти всегда, как говорит гуру C#, Джон Скит (Jon Skeet), очень плохо.

На рис. 5 показан код, который создает экземпляр Tweeter вместе с Tweet, и оба они используют перечисления. После сохранения этих данных вы увидите, что в базе данных значение ExperienceCode равно 1, а значение Rating — 2.

Рис. 5. Создание нового графа Tweeter и Tweet

var alias = new Tweeter
  {
    Name = "Julie",
    UserName = "Julie",
    Bio = "Mom of Giantpuppy",
    CreateDate = DateTime.Now,
    Experience = TwitterExperience.BeenAround,
    Tweets = new List<Tweet>{new Tweet
               {
                 Content = "Oh how I love that Giantpuppy",
                 CreateDate = DateTime.Now,
                 Rating = TweetRating.WatchOutAPlusK
               }}
  };

Вы можете использовать перечисления в запросах, и Entity Framework возьмет на себя преобразование перечисления в int-значения в SQL и последующее преобразование возвращаемых int-значений в значения перечислений. Вот пример LINQ-запроса, где в предикате Where используется перечисление:

context.Tweeters.Where(t => t.Experience == 
  TwitterExperience.Ninja)

В конечном коде на T-SQL предикат Where получит значение 2.

Более плавный переход на EF 5

Я уже слышала, что разработчикам не терпится перенести существующие решения на EF 5, чтобы задействовать преимущества поддержки перечислений и пространственных данных. Перенос таких проектов с EF 4 в EF 5, может быть, и не великая наука, но колдобин на этом пути хватает — у меня самой этот путь поначалу вызывал небольшое раздражение. Надеюсь, эта статья облегчит вам переход.

Мне нравится тот факт, что единый NuGet-пакет для поддержки Code First и DbContext предоставляет совместимые DLL как для .NET 4, так и для .NET 4.5. Даже если я использую EDMX, я все равно начинаю все новые проекты с применением DbContext, и поэтому 100% моих проектов теперь опираются на NuGet-пакет Entity Framework.

Помните, что приложения EF 4, выполняемые в системах с установленной .NET Framework 4.5, получат дополнительный выигрыш в производительности, поэтому, даже если вы еще не перешли на Visual Studio 2012, ваши пользователи уже сейчас прочувствуют некоторые усовершенствования в Entity Framework в .NET Framework 4.5.


Исходный код можно скачать по ссылке archive.msdn.microsoft.com/mag201209DataPoints.

Джули Лерман (Julie Lerman) — Microsoft MVP, преподаватель и консультант по .NET, живет в Вермонте. Часто выступает на конференциях по всему миру и в группах пользователей по тематике, связанной с доступом к данным и другими технологиями Microsoft .NET. Ведет блог thedatafarm.com/blog и является автором книг «Programming Entity Framework» (O’Reilly Media, 2010), «Programming Entity Framework: Code First» (O’Reilly Media, 2011), а также книги по DbContext (O’Reilly Media, 2012). Вы также можете читать ее заметки в twitter.com/julielerman.

Выражаю благодарность за рецензирование статьи эксперту Артуру Викерсу (Arthur Vickers).