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

Часто задаваемые вопросы по Entity Framework Code First и DbContext

Джули Лерман

 

Julie LermanНа этот раз я хотела бы поделиться ответами на вопросы, которые часто задают мне по поводу Code First и DbContext в Entity Framework 4.2. Для экономии места я буду ссылаться на ресурсы, где вы сможете найти более детальную информацию, дополняющую мои материалы.

Будут ли добавлены Code First и DbContext в .NET Framework?

Нет. Code First и DbContext будут распространяться отдельно от Microsoft .NET Framework, что позволит группе Entity Framework выпускать обновления по мере их готовности без ожидания следующего официального выпуска .NET Framework. Code First и DbContext API являются частью сборки EntityFramework.dll, которую можно добавлять в проекты динамически, используя NuGet. Установив расширение NuGet в Visual Studio, вы получаете Library Package Manager, который позволяет добавить в ваш проект ссылку на EntityFramework.dll. На рис. 1 показан пакет Entity¬Framework (в настоящее время его чаще всего скачивают из библиотеки пакетов NuGet) в Visual Studio Library Package Manager.

Installing Code First and DbContext into a Project via EntityFramework.dll
Рис. 1. Установка Code First и DbContext в проект через EntityFramework.dll

Подробнее о планах этой группы по выпуску обновлений функциональности Entity Framework и о том, почему они решили сосредоточиться на использовании NuGet для развертывания определенных типов функционала вне .NET API-средств, читайте статью в их блоге за октябрь 2011 г. «How We Talk About EF and Its Future Versions» (bit.ly/owoWgn).

Можно ли фильтровать связанные данные с помощью Include?

Нет. Метод Include позволяет интенсивно загружать связанные данные при выполнении запросов к Entity Data Model. К сожалению, с помощью интенсивной загрузки (eager loading) можно получать только полные наборы связанных данных. Это же относится и к отложенной загрузке (lazy loading), и — за единственным исключением — к явной загрузке (explicit loading). Это исключение таково: когда вы явно загружаете данные через API отслеживания изменений, вы можете применять метод Query с последующим фильтром Where:

context.Entry(personInstance)
  .Collection(p => p.Aliases)
  .Query()
  .Where(a=>a.FirstName == "Natasha")
  .Load();

Но подчеркиваю, что сделать это с помощью Include при интенсивной загрузке невозможно.

Так Include работал с момента первого выпуска Entity Framework в составе .NET Framework 3.5. Если вы хотите скомбинировать интенсивную загрузку с фильтрацией, подумайте о применении проекций. Подробнее на эту тему см. мою статью «Demystifying Entity Framework Strategies: Loading Related Data» (msdn.microsoft.com/magazine/hh205756) и публикацию в блоге «Use Projections and a Repository to Fake a Filtered Eager Load» по ссылке bit.ly/uZdnxy.

Как использовать лямбды с Include?

В случае ObjectContext единственный способ использования Include — передать строку, представляющую навигационное свойство, в которое нужно интенсивно загружать данные. Например, если у вас есть класс Road со свойством Trees, тип которого — ICollection<Tree>, вы запрашивали бы Roads с их свойствами Trees так:

context.Roads.Include("Trees")

Многие разработчики создали собственные методы расширения, позволяющие использовать вместо такого варианта строго типизированные выражения:

context.Roads.Include(r => r.Trees)

Возможность использовать лямбда-выражения теперь встроена в DbContext API. Соответствующие примеры вы найдете в шестой статье из серии чудесных публикаций Артура Викерса (Arthur Vickers) о DbContext API (bit.ly/wgI5zW). Однако ваши попытки использовать эту возможность могут оказаться безуспешными — как это случилось со мной. Пришлось смирить свою гордыню и обратиться за помощью к одному из членов группы за разъяснениями о поддержке лямбд. Оказалось, что, помимо ссылки на EntityFramework.dll, нужна директива using (или Imports в Visual Basic) для System.Data.Entity в том файле кода, где вы пытаетесь использовать Include совместно с лямбдой. Если вы хотите распространить интенсивную загрузку в набор на несколько уровней отношений с применением лямбд, то должны включать дополнительные методы Select. Например, если в вашем классе Tree есть ICollection <Leaf> и вы хотите интенсивно загружать листья (leaves) вместе с деревьями (trees), то строковое представление выглядело бы так:

context.Roads.Include("Trees.Leaves")

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

context.Roads.Include(r => r.Trees.Select(t => t.Leaves))

Хочу подчеркнуть: чем больше отношений вы пытаетесь интенсивно загружать с помощью Include, тем сложнее и медленнее может стать ваш SQL-запрос. Я всегда настоятельно рекомендую профилировать SQL-код, генерируемый Entity Framework (EF). О различных способах профилирования баз данных при использовании EF рассказывалось в этой рубрике за декабрь 2010 г.

Я была удивлена, узнав, что при использовании ObjectContext API ссылка на EntityFramework.dll плюс директива using для System.Data.Entity позволяют применять лямбды вместе с Include и в этом случае.

Стоит ли ждать .NET Framework 4.5, если нужно использовать Code First с WCF Data Services?

Для создания и использования WCF Data Services в .NET Framework 4 существует две сборки,System.Data.Services.dll и System.Data.Services.Client.dll. Если вы попытаетесь использовать их с классами DbContext и Code First в готовом виде, они работать не будут. Проблема с DbContext, а неCode First. Когда создавались эти сборки DbContext еще не было, поэтому они не понимают его. В марте 2011 г. Microsoft выпустила Community Technology Preview (CTP), где содержались исправленные сборки (Microsoft.Data.Services.dll и Microsoft.Data.Services.Client.dll), которые уже могли работать с DbContext. Поэтому вам нужно лишь заменить исходные сборки на новые и указать в своих сервисах данных DataServiceProtocolVersion как V3, а не V2. Вот пример того, как мог бы выглядеть простой сервис данных, предоставляющий контекст (PersonModelContext), производный от DbContext:

public class DataService : DataService<PersonModelContext>
{
  public static void InitializeService
  (DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("People", EntitySetRights.All);
    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
  }
}

Проблема с этим решением в том, что новые сборки являются не более чем CTP-версиями и не предназначены для производственных целей. Вам придется дождаться следующего RTM-выпуска (Released to Manufacturing) Data Services, чтобы задействовать их в производственной среде.

Но DbContext можно использовать со сборками System.Data.Services из .NET Framework 4. CTP-сборки всего-навсего помогают избавиться от написания дополнительного кода. Вам нужен лишь доступ к нижележащему ObjectContext, который поддерживает DbContext и предоставляет его сервису. В июле 2010 г. Роуэн Миллер (Rowan Miller), основной игрок на поле Code First в Microsoft, продемонстрировал это в своем блоге в статье «EF CTP4 Tips & Tricks: WCF Data Service on DbContext» (bit.ly/c2EA0l). Эта статья была написана для предварительной версии DbContext, поэтому я обновила его пример так, чтобы он работал с версией класса DbContext в Entity Framework 4.2, используя PersonModelContext — простую модель, которая предоставляет один DbSet для класса Person. Эти контекст и класс показаны на рис. 2.

Рис. 2. DbContext и простой класс, используемые сервисом данных

public class PersonModelContext : DbContext
{
  public DbSet<Person> People { get; set; }
}
public class Person
{
  public int PersonId { get; set; }
  [MaxLength(10)]
  public string IdentityCardNumber { get; set; }
  public string FirstName { get; set; }
  [Required]
  public string LastName { get; set; }
}

Затем сервис данных определяется для работы с ObjectContext, а не конкретно с PersonModelContext, производным от DbContext. Далее, переопределяя метод CreateData­Source сервиса данных, вы можете предоставить ObjectContext, который лежит в основе PersonModelContext, и тем самым обеспечить сервису необходимый ObjectContext, как показано на рис. 3.

Рис. 3. WCF-сервис данных, использующий DbContext с .NET Framework 4 Service API

public class DataService : DataService<ObjectContext>
{
  public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("People", EntitySetRights.All);
    config.DataServiceBehavior.MaxProtocolVersion = 
      DataServiceProtocolVersion.V2;
  }
  protected override ObjectContext CreateDataSource()
  {
    var dbContext = new PersonModelContext();
    var oContext = ((IObjectContextAdapter)dbContext).ObjectContext;
    return oContext;
  }
}

Поскольку меня прежде всего интересовал ObjectContext, это упрощенное представление кода, который может быть у вас в WCF Data Services.

Недостаток этого подхода в том, что вы не сможете использовать такую функциональность DbContext, как Validation (см. мою статью «Handling Entity Framework Validations in WCF Data Services» по ссылке msdn.microsoft.com/magazine/hh580732), поскольку сервис рассчитан на работу с ObjectContext.

Что использовать для конфигураций Code First — Data Annotations или Fluent API?

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

Code First расширяет Data Annotations, которые уже являются частью пространства имен System.ComponentModel.DataAnnotations в .NET Framework 4. В дополнение к аннотациям, таким как ValidationAttributes (Required, StringLength, RegEx), инфраструктура Entity Framework добавляет атрибуты для задания сопоставлений с базой данных (например, Column, Table и Timestamp) и другие специфичные для модели средства вроде ComplexType. Аннотации легко применять по отношению к классу, как в случае класса Person на рис. 2.

Возможные недостатки использования Data Annotations для конфигурирования ваших классов под Code First заключаются в следующем. Во-первых, добавление логики сохранения в бизнес-классы может оказаться весьма утомительным. Если вы зададите себе вопрос «какое отношение имя столбца в таблице базы данных имеет к моей модели предметной области?», то, вероятно, расхотите использовать Data Annotations. Во-вторых, эти сопоставления требуют ссылаться на EntityFramework.dll из сборок, содержащих ваши бизнес-классы. (Этого не потребуется в .NET Framework 4.5.) Это тоже вряд ли понравится вам, особенно если вы распространяете свое приложение между уровнями. Более того, Code First Data Annotations не предоставляют полного набора конфигураций, возможного при использовании Code First. Некоторые конфигурации, указывающие, например, специфические детали для сопоставления Table Per Hierarchy (TPH), могут быть реализованы только через Fluent API.

Мой совет разработчикам таков: выбирайте тот стиль, который вам нравится. Если вы предпочитаете выражать свои конфигурации через Entity Framework DbContext, используйте Fluent API. Если же вам нравится простота применения атрибутов и вы не против размещения логики сопоставления Code First в своих классах, применяйте Data Annotations. Если вы выбрали Data Annotations и наткнулись на конфигурацию, которую можно выразить только через Fluent API, то используйте комбинацию Data Annotations и Fluent API.

Можно ли использовать Code First с существующей базой данных?

Безусловно! Хотя по умолчанию Code First предназначен для создания за вас базы данных, вы можете указать существующую базу данных со своей строкой подключения и заставить Code First использовать ее. Кроме того, в Code First есть некоторые внутренние средства, позволяющие вам программным способом указывать базу данных. Например, вы можете перегрузить конструктор DbContext, чтобы он принимал имя базы данных или строку подключения к ней. Вы также можете указать и даже адаптировать типы DbContext ConnectionFactory (скажем, SqlConnectionFactory).

Вам надо будет убедиться, что соглашения Code First плюс любые добавленные вами конфигурации корректно представляют то, как ваши классы будут сопоставляться с существующей базой данных. Microsoft создала утилиту для декомпиляции базы данных на классы и текучие (fluent) конфигурации Code First, что может оказаться очень удобным. Узнать больше об этой утилите можно в моем блоге в статье «Quick Look at Reverse Engineer DB into Code First Classes» за май 2011 г. (bit.ly/lHJ2Or).

Указывая местонахождение базы данных, избегайте проверки схемы и инициализации базы данных, выполняемых DbContext по умолчанию при использовании Code First. Вы можете полностью отключить эту функциональность так:

Database.SetInitializer<MyContext>(null)

Подробнее на эту тему см. статью «Database Initializers in Entity Framework 4.1» и видеоролик в MSDN Data Developer Center (msdn.microsoft.com/data/hh272552). Более детальные примеры работы с классами ConnectionFactory и перегруженными версиями DbContext вы найдете в книге «Programming Entity Framework: Code First» (O’Reilly Media, 2011), которую я написала в соавторстве с Роуэном Миллером (Rowan Miller) из группы Code First.

Дополнительные ресурсы

Узнать о работе с Code First и DbContext можно во многих местах. В блоге группы Entity Framework blogs.msdn.com/adonet есть много примеров и схем разработки для различных ситуаций. О более продвинутых методиках можно почитать в блогах некоторых членов этой группы, например в блогах Роуэна Миллера (romiller.com) и Артура Викерса (blog.oneunicorn.com/author/ajcvickers). Форумы MSDN и StackOverflow.com по-прежнему являются отличными ресурсами, где можно многое узнать и задавать свои вопросы. Миллер и я недавно написали две небольшие книги: упомянутую выше «Programming Entity Framework: Code First» и «Programming Entity Framework: DbContext» (O’Reilly Media, 2012). Вы можете приобрести их в печатном виде или в цифровом формате.

Пытаться самостоятельно продираться через многие непонятные вещи нет никакой нужды.


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

Выражаю благодарность за рецензирование статьи эксперту Майку Фласко (Mike Flasko) и Диего Вега (Diego Vega).