Feel Good.

08 апреля 2011

Hello, Entity Framework "Code-First"

В этой статье рассмотрим простой пример, демонстрирующий новый способ разработки через "Code-first" для Entity Framework 4. В первую очередь, Entity Framework 4 Code-First нацелен на разработчиков, сфокусированных на Domain-driven design (DDD).
Предположим у нас имеется электронный магазин, в котором ведется два списка: список покупателей (Customers) и список заказов (Orders), которые храняться в базе данных ShopDb. Необходимо реализовать CRUD доступ над указанными сущностями в базе данных, используя весь потенциал Entity Framework 4.
Итак, в лучших традициях DDD начнем нашу работу с описания предметной области, для этого добавим в наш Solution проект (назовем его Domain), в который добавим два POCO (plain old CLR objects) класса Customer и Order, связанных отношением один-ко-многим:

public class Customer

{

    public int CustomerId { get; set; }

    public string ContactName { get; set; }

    public virtual ICollection<Order> Orders { get; set; }

}

public class Order

{

    public int OrderId { get; set; }

    public string Description { get; set; }

    public Customer Customer { get; set; }

}


Важно отметить тот факт, что наша модель не реализует сторонних интерфейсов, не наследуется от сторонних классов (например от EntityObject, как это было в предыдущей версии EF) и доступна для редактирования и полностью располагается в отдельной сборке (и мы можем легко экспортировать ее как часть в другой проект) и никаких auto-generated моделей и partial-классов, никаких T4 преобразований. Мы сосредоточились на предметной области и реализовали чистую модель и ничего лишнего.
Приступим ко второй части статьи: реализации CRUD функционала над предметной областью используя Entity Framework с новой возможностью code-first. Для этого добавим в solution новый проект со сылкой на наш Domain и System.Data.Entity (стандартное пространство имен для EF), в который добавим класс ShopDbContext:

public class ShopDbContext : DbContext

{

    public DbSet<Customer> Customers { get; set; }

    public DbSet<Order> Orders { get; set; }

}


Классы DbContext и DbSet это новые классы, добавленные в Entity Framework Code-first. Если проводить аналогию, то DbContext это аналог ObjectContext, контейнер наших сущностей, а DbSet аналог ObjectSet, черз который мы будем осуществлять все наши запросы. Если заглянуть глубже, то можно увидеть, что DbContext реалиует интерфейс IObjectContextAdapter, который содержит ссылку на уже знакомый нам ObjectContext.
EF уже содержит в себе все необходимые настройки по-умолчанию, и чтобы начать его использовать вам остается только указать строку соединения к базе данных:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <connectionStrings>

    <add name="ShopDbContext" connectionString="Server=Ilya\sqlexpress2005;Database=ShopDb;Trusted_Connection=True;" providerName="System.Data.SqlClient"/>

  </connectionStrings>

</configuration>


По-умолчанию, контекст EF выбирает ту строку соединения, имя которой совпадает с названием самого контекста, в нашем случае это ShopDbContext. И если мы запустим приложение, EF по-умолчанию выбирает стандартный маппинг и автоматически создает структуру базы, если такова отсутствует. В нашем случае будет создана новая база ShopDb, содержащая три таблицы: Customers, Orders и служебную EdmMetadata (таблица для идентификации текущего контекста).
Таблица EdmMetadata содержит хэш код от текущей EF модели, и если мы изменим модель, например добавим поле Address в Customer, то при следующем запуске приложения получим исключение:
The model backing the 'ShopDbContext' context has changed since the database was created. Either manually delete/update the database, or call Database.SetInitializer with an IDatabaseInitializer instance. For example, the DropCreateDatabaseIfModelChanges strategy will automatically delete and recreate the database, and optionally seed it with new data..
В исключении сказано, что EF-модель была обновлена, и текущая схема базы данных перестала соответствовать предметной области и ее необходимо пересоздать либо обновить.
По-умолчанию EF не знает, как выполнить подобный upgrade нашей БД, но мы можем ей это указать, передав в процедуру Database.SetInitializer(.) перед стартом приложения свой IDatabaseInitializer, или например воспользоваться уже готовым DropCreateDatabaseIfModelChanges.
Например, если мы хотим очищать базу каждый раз при обновлении модели:

Database.SetInitializer<ShopDbContext>(new DropCreateDatabaseIfModelChanges<ShopDbContext>());

12 комментариев:

  1. Привет. EF4CF по сравнению с fluent-nh полное говно. Извини, но это правда.

    ОтветитьУдалить
  2. Приветствую!
    Да, возможно вы правы, но пока я вижу, что Code-First это лучший вектор развития Entity Framework. И как не крути, а разработчики, которые используют EF по достоинству оценят CF.

    ОтветитьУдалить
  3. DDD это проектирование по модели, некий pure ООП. Вся бизнес логика лежит домене. Он никаким образом не может быть связан с POCO. Это две кардинально разные вещи.

    ОтветитьУдалить
  4. @Nikolay Sergeev
    Логика не обязана быть в домене (см http://www.martinfowler.com/bliki/AnemicDomainModel.html).
    А где я сказал что DDD связан с POCO?

    ОтветитьУдалить
  5. Ну, вы связали DDD и POCO в следующем предложении:
    "Итак, в лучших традициях DDD начнем нашу работу с описания предметной области, для этого добавим в наш Solution проект (назовем его Domain), в который добавим два POCO (plain old CLR objects) класса Customer и Order, связанных отношением один-ко-многим"

    Anemic(она же POCO) это полная противоположность DDD. В статье Фаулера по вашей ссылке он называет Anemic анти-паттерном и сообщает, что такие фанатики ООП как он и Эрик Эванс борются с Anemic со времен Smalltalk.

    У Эрика Эванса великолепная книжка по DDD. После ее прочтения вы бы ни за что не поставили слова POCO и DDD в один ряд.

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

    public class Customer
    {
    }

    public class Customer : EntityObject
    {
    }
    Более того, я могу наделить POCO класс Customer Rich-логикой, а могу и оставить его Anemic.

    ОтветитьУдалить
  7. Этот комментарий был удален автором.

    ОтветитьУдалить
  8. Хотя я поторопился, POCO вполне может быть DDD, если добавить в него поведение. Я почему то считал, что POCO и DTO это одно и тоже, но ошибался:

    http://stackoverflow.com/questions/725348/poco-vs-dto

    ОтветитьУдалить
  9. Спасибо за пост - достаточно лаконично и понятно

    ОтветитьУдалить
  10. Новичок в этой теме, поэтому появилось пару вопросов:
    1) для чего "virtual"?
    2) почему вместо List используется ICollection?

    ОтветитьУдалить
  11. @GLeBaTi
    Есть общие правила http://msdn.microsoft.com/en-us/library/dd468057.aspx.

    Если коротко:
    1) For lazy loading
    2) A navigation property that represents the "many" end of a relationship must return a type that implements ICollection, where T is the type of the object at the other end of the relationship.

    ОтветитьУдалить