Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Предварительный обзор Entity Framework: «code first», ObjectSet и DbContext
Джули Лерман
Пока Entity Framework (EF) 4 была все еще на стадии бета-версии, группа разработки начала работать над другим способом ее применения. Изначально мы располагали способом «database-first» (сначала база данных) для создания модели (обратным инжинирингом базы данных в Entity Data Model) и новым механизмом EF 4 «model-first» (сначала модель) (определяем Entity Data Model, затем создаем по ней базу данных). В дополнение группа решила создать нечто, что было названо «code first» (сначала код).
При подходе «code first» вы начинаете с кода — не с базы данных и не с модели. Фактически в этом случае у вас нет ни визуальной модели, ни XML, описывающего эту модель. Вы просто создаете классы для предметной области своего приложения и механизм «code first» позволяет им участвовать в работе инфраструктуры EF. Вы можете использовать контекст, писать и выполнять запросы LINQ to Entities и задействовать преимущества EF в отслеживании изменений. Но у вас нет нужды иметь дело с моделью в дизайнере. Если вы не создаете приложения со сложной архитектурой, то можете практически забыть о наличии в ней EF, которая берет на себя всю обработку взаимодействия с вашей базой данных и автоматически отслеживает изменения.
Приверженцам разработки, управляемой задачами предметной области (domain-driven development, DDD), видимо, понравится подход «code first». Фактически некоторые гуру DDD помогли группе EF понять, чем мог бы стать полезным этот подход для DDD-программистов. Подробнее на эту тему см. в блоге Дэнни Симмонса (Danny Simmons) статью о Data Programmability Advisory Council (blogs.msdn.com/b/dsimmons/archive/2008/06/03/dp-advisory-council.aspx).
Но «code first» не был готов для включения в Microsoft .NET Framework 4 и Visual Studio 2010. По сути, он еще развивается, и Microsoft предоставляет разработчикам доступ к соответствующему коду для экспериментов и обратной связи через Entity Framework Feature CTP (EF Feature CTP). Четвертая итерация CTP-версии (CTP4) выпущена в середине июля 2010 г. Изменения на этой итерации были весьма обширными.
Помимо включения в CTP4 сборки «code-first», Microsoft продолжила работу над некоторыми из новых идей для следующей итерации EF. Эти наработки также внесены в CTP4, чтобы с ними можно было поэкспериментировать и написать свои комментарии. Еще важнее, что «code first» использует преимущества некоторых из этих новых средств.
Целый ряд полезных статей, где описываются детали этой CTP-версии, вы найдете в публикациях группы EF (tinyurl.com/297xm3j), Скотта Гатри (Scott Guthrie) (tinyurl.com/29r3qkb) и Скотта Ханселмена (Scott Hanselman) (tinyurl.com/253dl2g).
В этой статье я хочу сосредоточиться на некоторых из новых средств и усовершенствований в CTP4, которые вызвали такую невероятную шумиху вокруг EF. А причина в том, что они кардинально упрощают разработку с использованием конкретно EF и .NET Framework в целом.
Улучшения в базовом API
Одно из значимых усовершенствований, добавленных CTP4 в исполняющую среду EF, — облегченные версии двух основных классов ObjectContext и ObjectSet. Первый поддерживает запросы, отслеживание изменений и запись обратно в базу данных, второй инкапсулирует наборы схожих объектов.
Новые классы — DbContext и DbSet — имеют те же важные функции, но не предоставляют вам доступа ко всему набору функциональности ObjectContext и ObjectSet. Не в каждом сценарии требуется доступ ко всем средствам в более надежных классах. DbContext и DbSet не являются производными от ObjectContext и ObjectSet, но DbContext позволяет легко обращаться к полнофункциональной версии через свойство DbContext.ObjectContext.
Основы ObjectSet и упрощенного DbSet
ObjectSet — обобщенное представление (в стиле набора) типов сущностей. Например, вы можете сделать так, чтобы ObjectSet<Customer> вызывал Customers, который является свойством класса контекста. Запросы LINQ to Entities пишутся применительно к ObjectSet. Вот запрос к Customers ObjectSet:
from customer in context.Customers
where customer.LastName=="Lerman"
select customer
У ObjectSet есть внутренний конструктор и нет конструктора без параметров. ObjectSet создается методом ObjectContext.CreateObjectSet. В типичном классе контекста свойство Customers будет определено так:
public ObjectSet< Customer> Customers {
get {
return _ customers?? (_customers =
CreateObjectSet< Customer >(" Customers"));
}
}
private ObjectSet< Customer > _ customers;
Теперь вы можете писать запросы к Customers и манипулировать набором, добавляя, присоединяя или удаляя объекты.
Работать с DbSet намного легче. Вы не можете сконструировать DbSet (по аналогии с ObjectSet), но эти объекты будут создаваться автоматически и присваиваться любым свойствам (с открытыми аксессорами set), объявленным в вашем производном DbContext.
Методы набора ObjectSet называются в терминах EF: AddObject, Attach и DeleteObject. Вот пример:
context.Customers.AddObject(myCust)
DbSet использует просто Add, Attach и Remove, что больше соответствует именам методов других наборов, поэтому вам не придется тратить время, гадая «как это может называться в EF»:
context.Customers.Add(MyCust)
Применение метода Remove вместо DeleteObject также более четко описывает, что делает данный метод. Он удаляет элемент из набора. Название DeleteObject предполагает, что будут удалены данные из базы данных, а это вводило в заблуждение многих разработчиков.
На данном этапе моей любимой особенностью DbSet является то, что он упрощает работу с производными типами. ObjectSet обертывает базовые типы и все типы, наследующие от него. Если у вас есть базовая сущность Contact и производная от него сущность Customer, то всякий раз, когда вам понадобится работать с клиентами, вам придется начинать с Contacts ObjectSet. Например, чтобы запросить клиентов, вы пишете context.Contacts.OfType<Customer>. Это не только запутывает — до этого еще и додуматься не так-то просто. DbSet позволяет обертывать производные типы, поэтому теперь вы можете создать свойство Customers, которое возвращает DbSet<Customer>, и взаимодействовать с ним напрямую, а не через набор Contacts.
DbContext — упрощенный контекст
DbContext предоставляет наиболее часто используемые средства ObjectContext, а также некоторые дополнительные, дающие реальную выгоду. Лично мне на данный момент в DbContext больше всего нравятся обобщенное свойство Set property и метод OnModelCreating.
Все ObjectSet, на которые я ссылалась до сих пор, имеют явные свойства экземпляра ObjectContext. Допустим, у вас есть модель PatientHistory с тремя сущностями: Patient, Address и OfficeVisit. И будет класс PatientHistoryEntities, производный от ObjectContext. Этот класс содержит свойство Patients, которое является ObjectSet<Patient>, а также свойства Addresses и OfficeVisits. Если вам нужно писать динамический код с использованием обобщений, вы должны вызывать context.CreateObjectSet<T>, где T — один из типов ваших сущностей. И вновь все это весьма запутанно.
В DbContext имеется более простой метод с именем Set, позволяющий вызывать context.Set<T>, который возвращает DbSet<T>. Вроде бы всего на 12 букв меньше, а путаницы больше нет. Вы также можете использовать производные сущности с этим свойством.
Еще один член DbContext — OnModelCreating, полезный при использовании подхода «code first». Любые дополнительные конфигурации, которые вы хотите применить к своей модели предметной области, можно определять в OnModelCreating прямо перед тем, как EF сформирует в памяти метаданные на основе классов предметной области. Это большой шаг вперед по сравнению с предыдущими версиями механизма «code first». Мы еще поговорим об этом подробнее.
«Code first» становится интеллектуальнее и проще в использовании
«Code first» был впервые представлен разработчикам в EF Feature CTP1 в июне 2009 г. под названием «code only» (только код). Основной посыл этого изменения названий заключался в том, что разработчики просто хотят определять классы своих предметных областей и что их не интересует физическая модель. Однако исполняющая среда EF полагается на XML такой модели, чтобы преобразовывать запросы к модели в запросы к базе данных, а затем запрашивать результаты от базы данных и записывать их обратно в объекты, описываемые моделью. Без этих метаданных EF не может выполнять свою работу. Но метаданные не обязательно должны находиться в физическом файле. EF считывает эти XML-файлы лишь раз при обработке приложения, создает строго типизированные объекты метаданных на основе полученного XML, а затем все взаимодействие осуществляется с XML в памяти.
Механизм «code first» тоже создает объекты метаданных в памяти. Но он не считывает XML-файлы, а извлекает метаданные из классов предметной области (рис. 1). С этой целью он использует специальное соглашение и предоставляет средства, с помощью которых вы можете добавлять дополнительные конфигурации для дальнейшей оптимизации модели.
Рис. 1. Механизм «code first» формирует метаданные Entity Data Model в период выполнения
Другая важная задача «code first» — использование метаданных для создания схемы базы данных и даже самой базы данных. Эти возможности предоставлялись еще в самой первой общедоступной версии.
Вот пример, где вам потребовалась бы какая-то конфигурация для преодоления некоторых неправильных допущений. У меня есть класс ConferenceTrack со свойством идентификации TrackId. По соглашению «code first» выполняется поиск Id или имени класса + Id как свойства идентификации, которое будет использоваться для EntityKey сущности и основного ключа таблицы базы данных. Но TrackId не укладывается в этот шаблон, поэтому я должна сообщить EF, что это мой ключ идентификации.
Новый класс ModelBuilder формирует модель в памяти на основе ранее описанных классов. Вы можете определять дополнительные конфигурации с помощью ModelBuilder. Я могу указать, что сущность ConferenceTrack должна использовать собственное свойство TrackId как свой ключ со следующей конфигурацией ModelBuilder:
modelBuilder.Entity<ConferenceTrack>().HasKey(
ct => ct.TrackId);
Теперь ModelBuilder будет учитывать эту дополнительную информацию, создавая модель в памяти и формируя схему базы данных.
Более логичное применение конфигураций
Здесь очень удобен DbContext.OnModelCreating. Вы можете поместить конфигурации в этот метод, чтобы они применялись при создании модели с помощью DbContext:
protected override void OnModelCreating(
ModelBuilder modelBuilder) {
modelBuilder.Entity<ConferenceTrack>().HasKey(
ct => ct.TrackId);
}
Другое новшество CTP4 — альтернативный способ применения конфигураций через атрибуты в классах. Эта методика называется «аннотации данных». Ту же конфигурацию можно получить, применив аннотацию Key непосредственно к свойству TrackId в классе ConferenceTrack:
[Key]
public int TrackId { get; set; }
Это, безусловно, проще, но лично я предпочитаю использовать программные конфигурации, чтобы в классах не было никакой специфики, связанной с EF.
Применение этого подхода также подразумевает, что DbContext позаботится о кешировании модели, чтобы последующее создание других DbContext не приводило к издержкам на повторное распознавание модели.
Упрощение отношений
Одно из наиболее значимых усовершенствований в «code first» заключается в том, что этот механизм стал гораздо «интеллектуальнее» строить предположения на основе классов. Хотя улучшений в этом направлении было внесено очень много, я заметила, что на мой код сильнее всего влияют расширенные соглашения по отношениям.
Хотя отношения определяются в ваших классах, в предыдущих CTP-версиях нужно было предоставлять конфигурационную информацию, чтобы определить эти отношения в модели. Кроме того, конфигурации не были ни элегантными, ни логичными. Теперь «code first» может корректно интерпретировать смысл большинства отношений, определенных в классах. Ну а в тех случаях, когда приходится вручную подстраивать модель с помощью дополнительных конфигураций, синтаксис значительно упрощен.
В моих классах предметной области определен целый ряд отношений через свойства. В ConferenceTrack, например, есть такое отношение «один ко многим»:
public ICollection<Session> Sessions { get; set; }
В Session есть обратное отношение, а также отношение «многие ко многим»:
public ConferenceTrack ConferenceTrack { get; set; }
public ICollection<Speaker> Speakers { get; set; }
Используя мои классы и единственную конфигурацию (определяющую TrackId как ключ для конференций), ModelBuilder создал базу данных со всеми отношениями и наследованиями, показанными на рис. 2.
Рис. 2. Структура базы данных, созданная ModelBuilder
Заметьте, что для отношения «многие ко многим» создана таблица Sessions_Speakers. В моей предметной области класс Workshop наследует от Session. По умолчанию «code first» предполагает наследование Table Per Hierarchy (таблица на каждую иерархию), поэтому он создал в таблице Sessions поле Discriminator. С помощью конфигурации я могу сменить его имя на IsWorkshop или даже указать, чтобы применялось наследование Table per Type (таблица на каждый тип).
Заблаговременная подготовка к применению новых средств
Эти выдающиеся новые средства, которые стали доступны уже в CTP4, могут стать источником путаницы для тех, кто заявляет, что хочет использовать их уже сегодня. Не забывайте, что это лишь предварительная версия и ее нельзя использовать в производственном коде, но определенно стоит исследовать ее на предмет пригодности для вас и написать свой отзыв группе EF; это поможет группе сделать все эти средства еще лучше. Вы уже сейчас можете начать подготовку к тому, как вы будете использовать «code first» и другие новшества EF в своих будущих приложениях.
Джули Лерман — Microsoft MVP, преподаватель и консультант по .NET, живет в Вермонте. Часто выступает на конференциях по всему миру и в группах пользователей по тематике, связанной с доступом к данным и другими технологиями Microsoft .NET. Ведет блог thedatafarm.com/blog и является автором очень популярной книги «Programming Entity Framework» (O’Reilly Media, 2009). Вы также можете встретить ее на julielerman..
Выражаю благодарность за рецензирование статьи эксперту Роуэну Миллеру (Rowan Miller)