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

Готовимся к будущей Entity Framework 7

Джули Лерман

Julie LermanРазработка следующей версии Entity Framework в самом разгаре. Я получила первое впечатление того, над чем работала группа EF, на конференции TechEd North America 2014, где менеджер программ Роуэн Миллер (Rowan Miller) рассказал о целях Entity Framework 7 (EF7) и продемонстрировал некоторые очень ранние наработки.

Я пишу эту статью спустя пять месяцев, и хотя EF7 все еще находится на стадии ранней альфа-версии, она уже прошла долгий путь. В этой статье я хочу дать вам представление о том, что принесет EF7 разработчикам, какие соображения стояли за принятыми решениями в отношении EF7 и что эта версия будет означать для существующих приложений, которые используют EF6 или более ранние версии. Я также гляну одним глазком на то, как будет выглядеть некоторый код.

Открытый исходный код, но теперь на GitHub

Прежде всего EF7 (подобно EF6) имеет открытый исходный код. Но разработка EF7 ведется не на CodePlex, а на GitHub (наряду с предстоящей версией ASP.NET). URL для разработки EF7: github.com/aspnet/EntityFramework. Как и в случае EF6, вы сможете видеть все детали EF7 по мере ее разработки. Вы можете изучать исходный код, а также наблюдать за прогрессом разработки через ветви (branches) и фиксации (commits), следить за дискуссиями, сообщать о проблемах, создавать альтернативный блок кода и отправлять группе запросы на проверку и потенциальное включение предлагаемого вами кода в кодовую базу.

EF6 в ближайшее время никуда не денется

Не волнуйтесь — вас не будут заставлять переходить на EF7. Вспомните ADO.NET-объекты DataSet и DataReader. Во многом по аналогии с ASP.NET Web Forms, которые поддерживаются до сих пор и даже получают более эффективный функционал благодаря эпизодическим оптимизациям, ADO.NET по-прежнему является частью Microsoft .NET Framework, хотя EF стала основной технологией доступа к данным в .NET на многие годы. Эти технологии не особо совершенствовались, но они все еще существуют и поддерживают огромные объемы устаревшего кода (включая мой). Одно из крупных преимуществ EF6 по сравнению с этими технологиями заключается в том, что она имеет открытый исходный код, поэтому, даже если группа EF в Microsoft не будет прикладывать особых усилий для развития EF6, сообщество все равно может заниматься этим. Но группа EF все же остается верна EF6. Она продолжит вносить оптимизации, внимательно анализировать запросы и обновлять EF6. Хотя группа основательно была занята EF7 большую часть 2014 года, EF6 все же обновлялась. Версия 6.1.была выпущена в феврале 2014 года, версия 6.1.1 — в июне, а на момент написания этой статьи появилась бета-версия 6.1.2, которая скоро будет выпущена. Поначалу я беспокоилась о том, смогу ли я поддерживать работу более старых приложений, но теперь эта озабоченность снята. Единственное, о чем я волнуюсь, — самые ранние приложения, которые использовали EF с .NET Framework 3.5, ObjectContext и т. д. Но если вы не обновляли эти приложения на использование всех крупных улучшений в EF за прошедшие годы, то в любом случае можете не особо беспокоиться о EF7. Кроме того, все прежние пакеты для EF можно найти в NuGet — вплоть до EF 4.1.10311.

EF7: краткий список

Вот краткий список наиболее интересных особенностей EF7:

  • поддержка нереляционных хранилищ данных и даже данных в памяти для тестирования;
  • поддержка машин и устройств, не использующих весь функционал .NET Framework. То есть вы можете использовать EF7 в приложениях Windows Phone и Windows Store, а также в системах Linux и Macintosh, где выполняется Mono;
  • поддержка многих средств, которые запрашивались разработчиками, но которые нельзя было реализовать на основе существующей кодовой базы;
  • продолжение поддержки приложений, в полной мере использующих .NET Framework, таких как WPF- и других клиентских приложений;
  • EF7 будет распространяться так же, как и ASP.NET 5, и может применяться в приложениях ASP.NET 5.

Привычные методики кодирования, но новая кодовая база

Инфраструктура EF развивалась с выходом каждой новой версии, в которых добавлялись новые возможности, оптимизировались API и повышалась производительность. Как я писала ранее в обзорной статье «Entity Framework 6: The Ninja Edition» за декабрь 2013 г. (bit.ly/1cwjyCD), новейшая версия вывела EF на новый уровень, придав этой инфраструктуре множество средств, о которых просили пользователи, например асинхронное выполнение операций с базой данных, встраивание в конвейер обработки запросов, поддержка адаптации соглашений Code First и многое другое. Глубокое рассмотрение этих средств вы найдете в моем учебном курсе для Pluralsight «Entity Framework 6, Ninja Edition: What’s New in EF6» (bit.ly/PS-EF6).

В той версии Microsoft хотела реализовать даже больше средств, чем просили разработчики, но кодовая база EF более чем десятилетней давности, на которую опирался EF, с зависимостью от ObjectContext и не столь гибкими шаблонами кодирования не позволяла группе перейти на этот новый уровень возможностей. Пришлось принять трудное решение (из-за которого многие из вас наверняка столкнулись с проблемами в своем устаревшем ПО) и вновь создать Entity Framework с нуля.

EF7 не создает новую инфраструктуру для доступа к данным. Вместо этого он образует новую, более надежную базу, благодаря которой не только поддерживаются средства и рабочий процесс прежней EF, но и появляется возможность создания гораздо большего функционала. В группе были продолжительные дискуссии на предмет того, должна ли эта версия стать следующей EF или новой технологией доступа к данным. В какой-то момент я даже поинтересовалась, не станет ли она чем-то вроде «EF Light». Но базовая функциональность EF никуда не делась, и после длительных размышлений я пришла к выводу, что данную инфраструктуру следует считать следующей версией Entity Framework. Подробнее на эту тему см. статью «EF7 — v1 or v7?» в блоге группы EF (bit.ly/1EFEdRH).

Избавление от всего устаревшего

Тем не менее, о EF7 есть новости, которые беспокоят некоторых разработчиков. Хотя большинство популярных классов, шаблонов и рабочих процессов EF останутся нетронутыми, некоторые из реже используемых членов будут удалены. Но, пожалуйста, не паникуйте; я вскоре расскажу об этом подробнее.

Позволить разработчикам по-прежнему применять привычные шаблоны и даже дать возможность портировать большие куски существующего кода было критически важной задачей в создании EF7. Вы сможете все так же использовать DbContext, DbSet, LINQ-запросы, SaveChanges и многие средства взаимодействия, которые долгое время были частью EF.

Вот класс DbContext, определенный мной в EF7:

public class BreweryContext : DbContext {
  public DbSet<Brewery> Breweries { get; set; }
  public DbSet<Beer> Beers { get; set; }
}

А вот простое обновление в EF7 — такое же, как в EF6. Я использую синхронное сохранение, но все асинхронные методы тоже есть в наличии:

public void StoreBeers(List<Beer> beers) {
  using (var context = new BreweryContext()) {
    context.Beers.AddRange(beers);
    context.SaveChanges();
  }
}

И простой запрос:

using (var context = new BreweryContext()) {
       return context.Breweries.Where(b=>b.Location.Contains("Vermont"));
}

Я использую версию EF7, которая находится в пакетах с версией beta2-11616. На самом деле EF7 не является бета-версией в настоящее время, а «beta2» относится к решению по именованию NuGet-пакетов. К моменту публикации этой статьи EF7 продвинется в своем развитии, так что считайте это мнением, а не обещанием.

У меня по-прежнему есть DbContext, и я определяю наборы DbSet, как и делала это всегда. OnModelCreating тоже на месте, хотя здесь я им не пользуюсь.

В EF4.1 введен DbContext API, который был гораздо больше сфокусирован на типичном применении EF. На внутреннем уровне он по-прежнему опирался на исходный ObjectContext, который обеспечивает взаимодействие с базой данных, управляет транзакциями и отслеживает состояние объектов. С тех пор DbContext стал классом по умолчанию для использования, и вы переходили на более низкоуровневые API, только если хотели выполнять редкие нестандартные операции с ObjectContext. EF7 освобожден от этого слишком «толстого» ObjectContext — останется лишь DbContext. Но некоторые операции, которые вы выполняли с помощью ObjectContext, по-прежнему останутся доступными.

Некоторые из очень сложных сопоставлений, которые трудно поддерживать и которые используются нечасто, будут удалены из EF7. В упомянутой ранее статье из блога говорится: «Например, у вас могла быть иерархия наследования, скомбинированная с сопоставлениями TPH, TPT и TPC, а также Entity Splitting, и все они находились в одной иерархии». Если вы когда-либо пытались работать напрямую с MetadataWorkspace API и сильно обжигались на этом, то знаете, что это сложная и запутанная штука, полезная для поддержки такого рода гибкости. Но чрезмерная сложность этого API не позволяла группе ввести поддержку других сценариев, о которой просили пользователи. Упростив возможности сопоставлений, группа сделала MetadataWorkspace API гораздо более простым и гибким. Вы можете легко получать метаданные о схеме своей модели от DbContext API в EF7, что дает возможность использовать на низком уровне продвинутые методики, обходясь без низкоуровневого ObjectContext.

Отказ от EDMX, но дальнейшее развитие Database First

В настоящее время в Entity Framework есть два способа описания модели. В одном из них используется EDMX в дизайнере, а другой охватывает классы, DbContext и сопоставления, применяемые в Code First API. Если вы работаете с EDMX и дизайнером, то в период выполнения EF создает модель в памяти на основе XML, стоящего за EDMX. Если вы выбираете путь Code First, EF создает ту же модель в памяти, считывая классы, DbContext и сопоставления, предоставленные вами. С этого момента EF работает одинаково, независимо от того, как вы описываете свою модель. Заметьте, что с помощью рабочего процесса EDMX/Designer, вы также получаете POCO-классы и DbContext, с которыми вы работаете в коде. Но поскольку здесь присутствует EDMX, они не используются для создания этой модели в памяти. Это важно понимать, когда вы будете читать следующее: EF7 не будет поддерживать модель EDMX на основе дизайнера. В ней не будет возможности считывать EDMX XML в период выполнения для создания модели в памяти. Она будет использовать лишь рабочий процесс Code First.

Когда группа сообщила об этом, это вызвало настоящую панику среди разработчиков. Отчасти это вызвано тем, что многие все еще не осознали, что базу данных можно преобразовать в POCO-классы, DbContext и сопоставления. Иначе говоря, вы можете начать с базы данных, чтобы получить модель Code First. Это было возможно с тех пор, когда впервые выпустили EF Power Tools Beta (в начале 2011 г.). И поддерживается дизайнером EF6.1, а также будет поддерживаться в EF7. Я много раз говорила, что термин «Code First» слегка сбивает с толку. Изначально он назывался «Code Only», но потом название сменили на Code First, чтобы оно сочеталось с Database First и Model First.

Так что вам не требуется дизайнер или EDMX, чтобы начать работу на основе существующей базы данных.

А как быть, если у вас имеются модели EDMX и вы не хотите терять возможность использовать дизайнер? Есть сторонние дизайнеры, которые поддерживают Entity Framework, например LLBLGen Pro Designer, который уже поддерживает EF Code First (bit.ly/11OLlN2), и Devart Entity Developer (bit.ly/1yHWbB2). Найдите эти инструменты и, возможно, какие-то еще, обеспечивающие потенциальную поддержку дизайнера для EF7.

Также стоит помнить и о другом пути: не переходить с EF6!

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

Кроме того, Microsoft стремится упростить распространение EF API. Папка NuGet-пакетов для EF6.1.1 занимает примерно 22 Мб. Сюда входят сборка (объемом 5,5 Мб) для .NET Framework 4.5 и еще одна сборка, если вы используете .NET Framework 4. В случае EF7 присутствует ряд DLL меньшего размера. Вы будете комбинировать только те DLL, которые нужны для поддержки вашего рабочего процесса. Так, если вы ориентируетесь на SQL Server, то должны использовать базовую EntityFramework.dll, для SQL Server и DLL с набором API, общих для реляционных хранилищ данных. Если вам надо задействовать миграции, это отдельная сборка. В ином случае вы можете предпочесть создание и выполнение миграций из Package Manager Console. Для команд существует свой API. С помощью диспетчера NuGet-пакетов необходимые пакеты будут идентифицированы и скачаны по их зависимостям, поэтому вам не придется особо заботиться о деталях.

Все это минимизирует занимаемую EF7 память на компьютере или устройстве конечного пользователя, что особенно важно на устройствах. ASP.NET идет по тому же пути. В обеих этих технологиях отказываются от их зависимости от полнофункциональной .NET Framework. Вместо этого в них предусмотрено распространение только с теми DLL, которые нужны для работы конкретного приложения. А значит, уже упрощенная версия .NET, используемая приложениями Windows Phone и Windows Store, сможет задействовать EF7.

Это также означает, что операционные системы вроде OS X и Linux, в которых применяется Mono, а не полнофункциональная .NET Framework, тоже смогут поддерживать Entity Framework на клиентской стороне.

За границами реляционных баз данных

Когда Entity Framework была выпущена впервые, Microsoft предполагала ее применение для разнообразных хранилищ данных, хотя первая версия работала фактически только с реляционными базами данных. Нереляционные базы данных уже существовали в то время, но не были широко распространены в отличие от баз данных NoSQL (особенно баз данных документов), которые столь популярны в наши дни.

Хотя EF — это Object Relational Mapper (ORM), разработчики, использующие эту инфраструктуру, хотят иметь возможность применять те же конструкции для взаимодействия с нереляционными базами данных. EF7 обеспечит высокий уровень их поддержки, но вы должны понимать, что на самом деле означает высокий уровень. Между реляционными и нереляционными базами данных существуют колоссальные различия, и в EF не станут пытаться маскировать эти различия. Но для базовой поддержки запросов и обновлений вы сможете задействовать уже привычные вам шаблоны.

На рис. 1 показан код из приложения-примера, ориентированного на Microsoft Azure Table Storage, которое является нереляционной базой данных документов. Этот пример взят из EF Program Manager Rowan Miller на github.com/rowanmiller/Demo-EF7. Заметьте, что пример выполняется в версии 11514 альфа-сборки EF7.

Рис. 1. DbContext, определенный для работы с Azure Table Storage

public class WarrantyContext : DbContext
{
  public DbSet<WarrantyInfo> Warranties { get; set; }
  protected override void OnConfiguring(DbContextOptions options) {
    var connection =
      ConfigurationManager.ConnectionStrings["WarrantyConnection"]
                        .ConnectionString;
    options.UseAzureTableStorage(connection);
  }
  protected override void OnModelCreating(ModelBuilder builder) {
    builder.Entity<WarrantyInfo>()
           .ForAzureTableStorage()
           .PartitionAndRowKey(w => w.BikeModelNo, w => w.BikeSerialNo);
  }
}

Метод OnConfiguring является новым. Он позволяет влиять на то, как EF конфигурирует DbContext в период выполнения, — нечто вроде того, что вы можете делать сегодня с классом DbConfiguration. Обратите внимание на метод расширения builder.UseAzureTableStorage, существующий потому, что я установила в свой проект и пакет EntityFramework.AzureTableStorage.

EF7 использует этот шаблон для различных провайдеров. Вот метод OnConfiguring класса DbContext в проекте, ориентированном на SQLite:

protected override void OnConfiguring(DbContextOptions builder) {
  string dir = ApplicationData.Current.LocalFolder.Path;
  string connection = "Filename=" + Path.Combine(dir, "VermontBrewery.db");
  builder.UseSQLite(connection);
}

В этом проекте установлен пакет EntityFramework.SQLite, поэтому теперь у меня другой метод расширения — UseSQLite.

Возвращаясь к классу WarrantyContext на рис. 1, можно заметить знакомое переопределение OnModelCreating для DbContext, и в нем я создаю специфическое сопоставление. И вновь я располагаю методами, предоставляемыми NuGet-пакетом EntityFramework.AzureTableStorage. То есть я отбираю пакеты, на основе которых я хочу создавать свой функционал. Azure Table Storage полагается в уникальной идентификации и поддержке разбиения таблиц на разделы на пары «ключ-значение». Чтобы извлечь или сохранить данные, важно знать, какие значения должны использоваться для PartitionKey и RowKey, поэтому в API есть метод PartitionAndRowKey, который позволяет сопоставлять свойства с соответствующими ключами. Эта концепция ничем не отличается от той, согласно которой вы могли использовать Fluent API или Data Annotations для указания свойства, сопоставляемого с основным ключом реляционной базы данных.

Благодаря этому сопоставлению можно написать привычный LINQ-запрос для получения каких-то данных:

var warranty = _context.Warranties
          .Where(w =>
            w.BikeModelNo == modelNo
            && w.BikeSerialNo == serialNo)
          .SingleOrDefault();

Здесь вы видите типичный LINQ-запрос, но он адресуется хранилищу данных Azure Table Storage. Этот пример кода обновляет объекты warranty, создает и вставляет новые объекты, используя DbSet.Add, и с помощью DbContext.SaveChanges сохраняет все обратно в хранилище данных — точно так же, как это делается сегодня с помощью EF6 (и всегда делалось на протяжении всей истории EF).

Интересно, что Entity Framework всегда поддерживала набор канонических средств для сопоставления с реляционными базами данных, но оставляла на усмотрение провайдеров баз данных то, как эти средства будут интерпретироваться. В EF7 появится высокоуровневый набор канонических средств, понятный реляционным и нереляционным хранилищам данных. Кроме того, имеется более низкоуровневый набор средств, сфокусированный на реляционных базах данных, и он инкапсулирован в сборке EntityFramework.Relational. Все провайдеры реляционных баз данных будут зависеть от этой сборки, и, как и сейчас, специфическая обработка взаимодействия с базой данных будет размещена в API провайдеров наподобие EntityFramework.SQLite, который я использовала в одном из примеров ранее. В провайдерах вы найдете методы расширения, которые выделены из метода AsRelational, а он является частью Relational API. Это метод расширения DbContext.

Есть даже провайдер хранилища данных в памяти, который предназначен для модульного тестирования, когда вы хотите избежать взаимодействия с базой данных и проверить только соответствующую логику. Как правило, в таких сценариях используются инфраструктуры имитации или заглушки для подмены реального взаимодействия с базой данных.

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

using (var context = new BreweryContext()) {
  // Выполняем какие-то действия над контекстом
}

Вы можете легко переключиться на хранилище в памяти, сначала установив в свой тестовый проект пакет entityframework.InMemory, определив DbContextOption для InMemoryStore, а затем указав, что контекст должен использовать этот вариант. И вновь это возможно благодаря методам расширения, предоставляемым этим API:

var options = new DbContextOptions().UseInMemoryStore();
using (var context = new BreweryContext(options)){
  // Выполняем какие-то действия над контекстом
}

Больше средств, больше возможностей, гораздо более высокая гибкость

Вы уже можете увидеть преимущества новой кодовой базы в гибкости, обеспечиваемой методами расширения, и в возможности влиять на конвейер Entity Framework с помощью перегруженной версии OnConfiguring. В новой кодовой базе также есть множество точек расширения — не только для изменения EF7, но и для упрощения включения собственной логики в EF7.

Новая кодовая база дает шанс группе EF решить некоторые застарелые проблемы. Например, в используемой мной версии уже есть поддержка пакетного обновления, которое применяется в реляционных базах данных по умолчанию. Я поэкспериментировала с кодом, позволяющим использовать свои методы, подставляемые прямо в LINQ-запросы без получения угрожающего сообщения «Entity Framework cannot translate this method into SQL». Вместо этого EF и провайдеры умеют разбирать, какая часть запроса станет SQL-кодом, а какая часть будет локально выполняться на клиенте. Уверена, что в этом варианте будет предусмотрена необходимая защита и соответствующие рекомендации, чтобы избежать потенциальных проблем с производительностью.

Группа смогла добавить долгожданную поддержку Unique Foreign Keys для моделей. Сейчас они анализируют возможность обеспечить поддержку функций с табличным значением (table-valued functions) и более четкие способы обработки отсоединенных данных, чему я посвятила немало времени за многие годы работы с Entity Framework. Это распространенная проблема с отсоединенными приложениями (не только при использовании Entity Framework), и не так-то просто создать алгоритмы, способные единообразно работать в каждом сценарии. Поэтому, безусловно, нужен новый подход.

В EF7 гораздо больше захватывающих возможностей. Я настоятельно советую внимательно изучить публикации в блоге группы ADO.NET по ссылке blogs.msdn.com/adonet. Помимо статьи, на которую я уже ссылалась, Роуэн Миллер подробно обосновал решение отказаться от поддержки дизайнера в EF7 (см. «EF7 — What Does ‘Code First Only’ Really Mean» по ссылке bit.ly/1sLM3Ur). Следите за этим блогом, а также за проектом на GitHub. В вики на GitHub (bit.ly/1viwqXu) есть ссылки на то, как получить доступ к ежевечерним сборкам, как скачать, скомпилировать и отладить исходный код; кроме того, в ней имеются руководства и заметки по проведению технических встреч. Группа нуждается в обратной связи с вами и с радостью принимает обоснованные запросы.

Трудные решения

Мне было важно написать о EF7, чтобы снять некоторые озабоченности о столь крупных переменах и о том, что некоторые из существующих средств EF, которые могут быть неотъемлемой частью ваших приложений, не войдут в EF7. Эти озабоченности небеспочвенны, и группа не относится к ним беспечно. Но понимание того, что EF6 никуда не денется и что ее развитие продолжится самим сообществом, крайне важно. Если вы хотите задействовать преимущества новой версии, вам придется принять ряд трудных решений. Обновить крупные приложения будет нелегко, и вы должны тщательно взвесить все за и против каждого варианта. Возможно, вы разрушите свое приложение, переписав лишь некоторые его части под EF7.

Повторю: на момент написания этой статьи EF7 все еще находился в стадии ранней альфа-версии, и я не уверена, насколько далеко продвинется работа над ней к тому времени, когда вы будете читать мою статью. Но текущий источник доступен, а NuGet-пакеты можно изучать, экспериментировать с ними и сообщать свои замечания и предложения. Учтите, что группа не всегда может поддерживать актуальность API всех провайдеров (например, для Redis, SQLite и прочих), поскольку они постоянно совершенствуются свои базовые API. Согласно статье «EF7 — Priorities, Focus and Initial Release» (bit.ly/1ykagF0), первый выпуск EF7 будет сфокусирован на совместимости с ASP.NET 5. В последующие выпуски будут добавлять больше средств. Тем не менее, хотя EF7 пока не достаточно стабильна, чтобы начать разработку приложений на ее основе, вы определенно должны приступить к ее изучению, чтобы заблаговременно спланировать будущую работу.


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

Выражаю благодарность за рецензирование статьи эксперту Microsoft Роуэну Миллеру (Rowan Miller).

Эта статья написана на основе альфа-версии Entity Framework 7. Любая изложенная здесь информация может быть изменена.