что такое code first
Разработка REST API — что такое Code First подход?
Это четвертая статья в серии статей по REST API:
Разработка хорошего REST API важна для того, чтобы иметь хорошие микросервисы. Подход Code-First фокусируется на генерации контракта из кода. Это наилучший из возможных подходов?
Вы изучите
Что такое Code-First подход?
Всякий раз, когда вы разрабатываете сервис, такой как REST API или SOAP API, вы можете выбрать один из двух подходов:
Spring Boot Code First пример REST API
Мы разрабатываем RESTful веб-сервис, используя Spring Boot Framework для генерации API. Например, в API retrieveAllUsers () мы открываем URI «/users» и
возвращаем всех пользователей (ресурс /users),
вызывая метод сервиса.
Когда мы переходим на этот URL, мы возвращаем всех пользователей:
Аналогичным образом определены и другие сервисные методы, каждый из которых имеет свой собственный URI. В этом примере мы берем код и генерируем из него документацию. В этой документации указано, как пользователь может использовать сервис. Для этого мы используем формат документации Swagger:
Swagger позволяет нам генерировать документацию из кода. Например, вот что Swagger генерирует для запроса на получение всех пользователей:
Он выводит тип получаемого нами ответного сообщения и сопровождающий его статус ответа. Вы даже можете вызвать этот сервис из Swagger получить ответ:
Вы также можете отправить запрос POST в «/users«:
Swagger сообщит нам, как структурировать сообщение запроса и указать внутри него отдельные форматы полей. Он также сообщит вам тип ответа, который вы получите, вместе с кодом ответа. То, что Swagger генерирует из кода, называется контрактом.
Преимущества Code First
Основные преимущества этого подхода:
Недостатки Code First
Недостатки этого подхода заключаются в следующем:
Нет параллельной разработки
Производитель услуг и потребители услуг не могут разрабатывать параллельно. Сначала необходимо разработать сервис, затем сгенерировать контракт, и только после этого можно написать код потребителя, который будет придерживаться контракта. Без понимания контракта потребитель не может быть разработан.
Нет цели для команд
Поскольку договор не может быть известен до того, как сервис будет разработан, не существует цели для различных заинтересованных сторон в разработке. Следовательно, есть все шансы, что направления будут отклоняться, и будут внесены ненужные изменения, что приведет к напрасной трате усилий.
Нет кроссплатформенной совместимости
На некоторых старых платформах не так просто сгенерировать контракт из кода. В результате этого для сгенерированных контрактов довольно часто возникает несовместимость между платформами.
По этому вопросу имеется авторское видео.
Резюме
В этой статье мы исследовали Code First подход построения REST API. Несмотря на то, что подход, основанный на коде, эффективен с точки зрения разработчика, он сталкивается с серьезными проблемами, когда речь идет о совместной разработке поставщика и потребителя.
Разработка баз данных с Code First
Повсеместно принято, что в «серьезных» CRUD приложениях база данных становится во главу угла. Ее проектируют самой первой, она обрастает хранимыми процедурами (stored procedures), с ней приходиться возиться больше всего. Но это не единственный путь! Для Entity Framework есть Code First подход, где главным становится код, а не база. Преимущества:
Есть и пара недостатков, но они скорее связаны с Entity Framework, а не с Code First подходом как таковым; о них чуть позже.
Ниже я покажу на примере, насколько просто разрабатывать с Code First подходом.
Пример
Возьмем простую модель:
В качестве фронт-энда будет ASP.NET MVC, так что создаем соответствующий проект. Выбираем No Authentication — в этом проекте нельзя будет логиниться и весь контент доступен для всех.
Я сделал еще 2 проекта — для бизнес-объектов и DAL, но при желании можно просто создать соответствующие папки в web проекте. Не забудте установить Entity Framework в соответствующие проекты через NuGet.
Создаем классы, которые будут отображать сущности (entities):
Как видно, все повторяющееся свойства (properties) можно убрать в абстрактный класс и наследоваться от него. В данном случае у каждой таблицы будет Primary Key колонка типа Guid, который будет генерироваться при записи в базу.
Grade — это просто энумератор, ничего особенного:
Создаем контекстный класс:
Отношения дефинированы через Fluent API, читаются с конца — например, Student — Enrollment относятся как one (Student): many (Enrollment).
Стоит отметить, что конфигурировать модели можно как через Fluent API, так и аннотациями. Для некоторых настроек аннотаций не существует, но их можно создать самим. Я предпочитаю все-же Fluent API.
И, наконец, заполнение базы данными:
Примечание: как следует из названия DropCreateDatabaseIfModelChanges, база будет дропаться при изменениях в соответствующих классах моделей. То есть данные — капут.
Как реализовать миграции, чтобы данные не капут, выходит за область этой статьи.
Последнее, что надо сделать — добавить информацию в web.config. Используем LocalDb, которая идет вместе с Visual Studio, которой вполне достаточно для целей этого проекта. Следующий код идет в элемент configuration:
А следующая разметка — в элемент entityFramework:
В атрибуте type элемента context указываются через запятую название класса контекста и assembly, где этот класс находится. То же самое для инициализатора в элементе databaseInitializer.
Это вообщем-то и все, проект готов к запуску.
Скачать пример можно тут.
Недостатки
Во-первых, к существующей базе данных подобный подход применить сложно. Так что вообщем-то это для разработки с нуля.
Часто подножки ставит Entity Framework, который часто принимает решения за программиста — есть так называемые конвенции, что, допустим, property который называется Id, будет по умолчанию преобразован в Primary Key таблицы. Мне такой подход не нравится.
Продолжение темы
Разработка с помощью Code First подхода в Entity Framework достаточно объемная тема. Я не касался вопроса миграций, проблем с многопоточностью (concurrency) и многого другого. Если сообществу интересно, я могу продолжить эту тему в дальнейших статьях.
1. Getting started with Entity Framework 6 Code First using MVC 5
2. Database initialization in Code-First
3. Lerman J., Miller R. — Programming Entity Framework. Code First (2011)
Использование Code-First
В предыдущей статье мы кратко рассмотрели структуру различных подходов для работы с Entity Framework. В этой статье мы более подробно остановимся на подходе Code-First и рассмотрим примеры работы с Code-First. Мы создадим две таблицы, описывающие данные заказчика в интернет магазине и его заказами. Отношение между этими таблицами будет “один ко многим” (one-to-many).
Создание проекта
Во всех статьях на нашем сайте, которые посвящены описанию Entity Framework, мы используем проект простого веб-приложения ASP.NET. В этом разделе мы покажем как создать этот проект, а позже будем ссылаться на эту статью, чтобы каждый раз не описывать одни и те же шаги:
Запустите Visual Studio 2012 (в примерах всех статей мы будем использовать версию Visual Studio 2012 в сочетании с Entity Framework 6).
Добавьте в решение проект, имеющий шаблон библиотеки классов (Class Library) и назовите его CodeFirst:
Добавьте в новый проект файл класса Model.cs, в котором мы будем описывать модель данных.
Добавьте ссылку на проект CodeFirst в базовом проекте веб-приложения. Для этого щелкните правой кнопкой мыши по вкладке References в окне Solution Explorer базового проекта и выберите пункт Add Reference. В открывшемся диалоговом окне перейдите на вкладку Solution и выберите проект CodeFirst.
Определение классов модели
Как вы уже знаете, при использовании подхода Code-First сначала определяется модель в коде, а затем, на ее основе создается (или модифицируется) база данных. Для нашего примера потребуется создать два класса, описывающих заказчика и его товары. Эти классы добавляются в файл Model.cs, созданный в предыдущем разделе:
Класс Order содержит идентификатор заказа, который позволяет уникальным образом распознать каждый заказ в таблице. Кроме того этот класс содержит свойства, описывающие название товара, его количество, описание и дату заказа. Также здесь указана ссылка на покупателя в виде свойства Customer.
Установка Entity Framework 6 в проект
Всякий раз, когда вам впервые понадобится использовать Entity Framework в проекте при подходе Code-First, вы должны будете добавить ссылки на библиотеки EF, после чего можно будет работать с Entity Framework в коде. Используйте для этого следующие шаги:
Начиная с версии 4, библиотека Entity Framework входит в удобный менеджер пакетов NuGet. Чтобы добавить поддержку в Entity Framework с помощью NuGet, выберите в окне Solution Explorer проект CodeFirst, щелкните по нему правой кнопкой мыши и выполните команду из контекстного меню Manage Nuget Packages.
После этого появится окно с описанием лицензии на использование Entity Framework. Согласитесь с условиями и на жмите кнопку “I accept”, после чего NuGet установит в ваш проект Entity Framewrok.
Повторите эти действия без 4 пункта для установки Entity Framework в проект ASP.NET.
Класс контекста данных
Сами по себе классы модели, созданные ранее, не имеют ничего общего с Entity Framework. На данном этапе они просто описывают структуру бизнес-модели, которая используется в приложении. Чтобы Entity Framework был в курсе, что эти классы служат также для управления данными в базе данных, нужно использовать класс контекста. EF имеет два базовых класса контекста:
ObjectContext
Этот класс является более общим классом контекста данных, и используется начиная с самых ранних версий Entity Framework.
DbContext
Этот класс контекста данных появился в Entity Framework 4.1 и он обеспечивает поддержку подхода Code-First (ObjectContext также обеспечивает работу подхода Code-First, но он труднее в использовании). Далее мы будем использовать DbContext.
Для создания класса контекста добавьте следующий новый класс SampleContext в проект CodeFirst:
Этот небольшой класс контекста представляет полный слой данных, который можно использовать в приложениях. Благодаря DbContext, вы сможете запросить, изменить, удалить или вставить значения в базу данных. Обратите внимание на использование конструктора в этом классе с вызовом конструктора базового класса DbContext и передачей ему строкового параметра. В этом параметре указывается либо имя базы данных либо строка подключения к базе данных (Entity Framework достаточно интеллектуален чтобы отличить тип параметра). В данном случае мы указываем явно имя базы данных, т.к. по умолчанию, при генерации базы данных Entity Framework использует имя приложения и контекста данных (например CodeFirst.SampleContext), которое нам не подходит.
После этого скомпилируйте приложение (горячая клавиша F6 ), чтобы исключить наличие ошибок на данном этапе.
Работа с данными
В данный момент у вас уже есть классы данных и контекста, необходимые для работы с базой данных, которая еще не существует. Простое создание этих классов не приводит к автоматическому созданию (изменению) базы данных при запуске приложения. Чтобы создать базу данных, нужно добавить код работы с данными с помощью Entity Framework, например, написать код вставки данных в таблицу. Имея это в виду, ниже описаны шаги, с помощью которых мы добавим веб-форму в наш проект в которой будем взаимодействовать с базой данных:
Измените разметку формы на ту, которая показана в примере ниже:
Здесь мы просто добавляем форму, с помощью которой можно вставить данные заказчика в таблицу Customers.
Теперь нужно добавить в файл отделенного кода веб-формы Default.aspx.cs обработку данных формы:
В этом коде мы используем механизм привязки моделей ASP.NET – очень удобного средства, пришедшего из ASP.NET MVC. Поле Photo хранит массив двоичных данных файла и для него привязка не работает, поэтому картинку в виде двоичного объекта мы загружаем вручную. Мы специально усложнили пример, добавив поле типа byte[], чтобы показать широкие возможности Entity Framework по поддержке работы с базами данных (в частности в этом случае поле Photo будет проецироваться в базе данных на объект BLOB (Binary Large OBject), имеющий тип VARBINARY(max)).
Код работы с Entity Framework в этом примере создает объект контекста SampleContext и использует его, чтобы добавить новые данные в таблицу Customers. Вызов метода SaveChanges() сохраняет изменения в базе данных и при первой вставке данных вызов SaveChanges() создаст базу данных.
Запустите это приложение, введите данные в форму и нажмите кнопку “Вставить в БД”:
После отправки формы вы можете повторить действия и вставить новых заказчиков. Обратите внимание, что при первой отправке возникает заметная задержка в ответе от сервера – именно в данной точке и создается база данных. Когда вы отправляете форму второй раз, эта задержка исчезает, т.к. данные вставляются в уже существующую базу данных.
Соглашения о конфигурации сгенерированной базы данных
После проделанных действий особый интерес вызывает структура сгенерированной базы данных. Чтобы проверить, что код выполнился правильно, откройте окно Server Explorer в Visual Studio, нажмите на кнопке Connect to Database, в открывшемся окне выберите поставщик SQL Server и настройте подключение к созданной базе данных MyShop, используя имя сервера “.\SQLEXPRESS” в окне Add Connection. После этого в окне Server Explorer отобразится новое подключение:
Обратив пристальный взгляд на структуру таблиц, вы сможете увидеть некоторые соглашения о проецировании сущностных классов, которые используются в Code-First. Например, свойства CustomerId и OrderId в сущностном классе Entity Framework преобразовал в первичные ключи в базе данных (EF автоматически ищет подстроку “Id” в именах свойств модели с помощью механизма рефлексии). Эти поля используют автоинкремент с помощью инструкции IDENTITY (1,1) и не могут иметь значение NULL.
в Entity Framework позволяют организовать взаимодействие между таблицами базами данных. Как вы уже видели, чтобы в родительском классе сослаться на другой связанный класс, навигационное свойство помечается как виртуальное. В Code-First есть также различные свойства для реализации отношений 0..1-1, 1-1 и many-to-many в таблицах, с которыми мы познакомимся позже, при подробном изучении аспектов Entity Framework.
Обратите внимание также на то, что Entity Framework сгенерировал еще одну таблицу с названием __MigrationHistory. Эта таблица хранит различные версии изменения структуры базы данных. В частности, в поле Model эта таблица хранит метаданные модели, представленные в виде двоичного объекта BLOB. Если позже вы измените модель в своем коде, Entity Framework вставит в эту таблицу новую запись, с метаданными новой модели.
Переопределение соглашений о конфигурации базы данных
Все указанные в предыдущем разделе соглашения можно переопределить с помощью средств Entity Framework. Для этого используются аннотации в виде атрибутов C# или строгая типизация с помощью Fluent API.
Использование аннотаций метаданных
Аннотации метаданных являются простейшей формой конфигурации и применяются непосредственно к классам и свойствам класса в виде атрибутов. Эти атрибуты доступны в приложении при наличии ссылок на сборки System.ComponentModel.DataAnnotations.dll и EntityFramework.dll. Помните, что аннотации позволяют простым образом настраивать конфигурацию Entity Framework, но при этом, с помощью аннотаций нельзя настроить некоторые сложные моменты, для которых используется Fluent API.
Как уже было сказано выше, аннотации используют синтаксис атрибутов языка C# или Visual Basic. В случае если вы не знакомы с использованием атрибутов, в C#, атрибуты применяются с использованием квадратных скобок. Например, аннотация данных для идентификации ключа в C# описывается как:
а в Visual Basic, используются угловые скобки:
В атрибутах может использоваться именованный параметр, в C# это выражается с помощью знака равенства:
в то время как Visual Basic использует двоеточие перед знаком равенства:
Давайте используем аннотации метаданных к нашему классу Customer. Есть несколько вещей, которые я бы хотел изменить в этом классе:
Поле Name не должно иметь значение NULL в таблице базы данных и длина поля не должна превышать 30 символов.
Максимальная длина поля email не должна превышать 100 символов.
Поле Age должно находиться в пределах от 8 до 100.
Фотография должна хранится в типе изображения Image SQL Server, а не в VARBINARY (max).
Для этого сначала нужно добавить ссылки на пространства имен System.ComponentModel.DataAnnotations и System.ComponentModel.DataAnnotations.Schema в файле Model.cs приложения CodeFirst. После этого измените структуру класса Customer, чтобы указать условия, приведенные выше:
Атрибут Required не нуждается в указании дополнительных параметров, в то время, как в атрибутах MaxLength, Range и Column эти параметры указаны. Атрибут Column влияет на схему базы данных (в частности позволяет менять тип данных столбца таблицы). Поэтому данный атрибут находится в пространстве имен System.ComponentModel.DataAnnotations.Schema, в отличие от других атрибутов. Аннотации являются компонуемыми, это означает, что вы можете использовать несколько атрибутов к одному свойству или классу, как это было сделано для свойства Name.
Помимо атрибутов, которые могут влиять на структуру базы данных, в пространстве имен System.ComponentModel.DataAnnotations существует атрибуты, которые Entity Framework просто игнорирует (атрибут Range, показанный выше, не влияет на структуру таблицы). Например, к типу Name можно использовать атрибут [MinLegth(3)], чтобы нельзя было указать в качестве имени покупателя строку, короче 3 символов. В T-SQL нельзя напрямую указать минимальную длину поля (хотя существует обходное решение с использованием инструкции CASE), поэтому Entity Framework будет игнорировать этот атрибут. Это показывает, что атрибуты метаданных, являются универсальными и могут использовать не только Entity Framework. Например, атрибуты MinLegth и Range могут использоваться в клиент-серверном коде проверки модели ASP.NET.
После того, как мы внесли изменения в модель данных, будет интересно как Enity Framework изменит структуру базы данных. Если вы теперь запустите приложение и попытаетесь вставить нового заказчика, то вы получите исключение InvalidOperationException. Мы ничего плохого не сделали с моделью данных, проблема заключается в способе инициализации базы данных, используемом в Code-First. Ниже показан текст исключения:
У вас есть несколько вариантов решения этой проблемы. Вы можете вручную удалить базу данных и запустить проект снова, при первой вставке данных сгенерируется новая база данных. Очевидно, что это решение требует определенных затрат времени и может создать проблемы блокировки файлов, если вы попытаетесь запустить приложение слишком быстро после удаления базы данных.
Другим решением проблемы, которое мы будем использовать в нашем приложении, является использование возможности Code-First обнаруживать изменения в модели данных и автоматически удалять и генерировать новую базу данных. Поведением Code-First в данной ситуации можно управлять с помощью статического метода SetInitializer() класса Database. По умолчанию, этому методу передается экземпляр класса CreateDatabaseIfNotExists, который указывает, что создавать базу данных нужно только в том случае, если базы данных не существует, а если модель изменилась, то нужно сгенерировать исключение.
Давайте изменим это поведение и передадим этому методу экземпляр объекта DropCreateDatabaseIfModelChanges, который указывает Code-First, что при изменении модели данных, базу данных нужно будет удалить и воссоздать с новой структурой. Метод SetInitializer() должен вызываться до непосредственной работы с Entity Framework, в нашем случае мы можем вызвать его в начале метода Page_Load:
Теперь, когда вы запустите приложение и попытаетесь вставить данные, Code First найдет разницу в новой модели и, с разрешения инициализатора, удалит и заново создаст базу данных. Если вы открыли таблицу базы данных для чтения данных где-то еще (например, в окне Server Explorer среды Visual Studio), Code First будет не в состоянии удалить базу данных, т.к. она используется другим процессом. В этом случае, возникнет заметная задержка при вставке данных, пока EF пытается удалить базу данных, а затем в конце концов будет сгенерировано следующее исключение:
Для решения этой проблемы зачастую хватает закрыть и открыть снова проект в Visual Studio.
Я также рекомендую вам рассмотреть возможность подхода к работе, когда вы вручную изменяете структуру базы данных с помощью T-SQL, а затем отражаете эти изменения в классах модели. Такой подход к работе еще называют Code-Second. Этот подход лишает вас одного из главных преимуществ Code-First – отсутствие непосредственной работы с базой данных, но он является единственной возможностью изменить структуру базы данных не удаляя ее (чтобы не удалять уже вставленные данные).
Допустим, нам нужно изменить структуру таблицы Customer изменив имя столбца Name на FirstName и добавив новый столбец LastName. Чтобы воспользоваться подходом Code-Second выполните следующие шаги:
Используя программу SQL Server Management Studio или окно Server Explorer в Visual Studio удалите таблицу __MigrationHistory.
Выполните следующий SQL-код для изменения структуры таблиц (для этого щелкните правой кнопкой мыши по имени таблицы Customer в окне Server Explorer и выберите из контекстного меню команду New Query):
Обновите модель данных, чтобы отразить эти изменения:
Далее нам понадобится изменить разметку веб-формы и код обработчика, чтобы использовать эти изменения:
В коде обработчика Default.aspx.cs удалите вызов метода SetInitializer().
После этого вы можете запустить приложение и убедиться, что вставка данных в базу работает корректно и при этом данные, которые мы вставили ранее никуда не делись.
Использование Fluent API
Настройка соглашений о конфигурации базы данных с помощью аннотаций является довольно простой, но она не может обеспечить проецирование сложной конфигурации, например создания отношения many-to-many между таблицами. дает вам доступ к таким глубоким настройкам, но при этом является более сложным в использовании.
Концепция Fluent API заключается в вызове ряда стандартных методов для описания настройки конфигурации базы данных. Использование средства IntelliSense в редакторе кода Visual Studio облегчает использование этих методов. Например, в Code-First Fluent API, вы можете использовать метод Entity(), чтобы выбрать объект для настройки. IntelliSense покажет вам все методы, которые могут быть использованы для настройки объекта. Если вы затем используете метод Property(), чтобы выбрать свойство объекта сущности, то вы увидите все доступные настройки для конфигурирования.
Еще одним преимуществом Fluent API перед аннотациями является то, что он не засоряет код модели и организует взаимосвязь модели с контекстом данных.
Когда приходит время для построения модели, DbContext смотрит на структуру классов модели. Fluent API позволяет вмешаться в этот процесс и передать контексту дополнительные данные для конфигурации. Это становится возможным, благодаря переопределению метода DbContext.OnModelCreating(), который вызывается перед тем, как контекст построит сущностную модель данных. Этот метод является виртуальным, так что вы можете изменить его и вставить собственную логику, используя средства Fluent API.
Структура этого метода следующая:
Параметр типа DbModelBuilder в этом методе, позволяет добавлять настройки конфигурации. Методы класса DbModelBuilder являются обобщенными и поддерживают указание лямбда-выражений в качестве параметров (принимают типы делегатов), благодаря чему обеспечивается быстрая настройка конфигурации. Например, следующий код позволяет настроить таблицу Customer, аналогично показанным ранее атрибутам в модели:
Подходы с использованием аннотаций и Fluent API можно использовать в Code-First, преимущество при указании одинаковых правил отдается Fluent API.
Если у вас используется много настроек конфигурации Fluent API, то код метода OnModelCreating() может быстро стать перегруженным. Можно организовать код настроек Fluent API с помощью создания специальных классов конфигурации, унаследованных от EntityTypeConfiguration. Например, мы добавили следующий файл EntityConfigurations.cs в проект CodeFirst:
Чтобы добавить эту конфигурацию, в вызове OnModelCreating() можно использовать свойство Configurations класса DbModelBuilder:
На этом мы заканчиваем краткое рассмотрение подхода для работы с Code-First, позже, при описании Entity Framework, мы более подробно познакомимся со всеми деталями использования аннотаций данных и Fluent API.