Пошаговый обзор стратегии Code First в Entity Framework 4.1
Примечание переводчика. Данная статья является вольным адаптированным переводом статьи EF Feature CTP5: Code First Walkthrough. Адаптация заключалась в том, что к моменту перевода была уже выпущена Entity Framework версии 4.1, а в оригинальной статье дан обзор Code First на основе промежуточной версии Entity Framework Feature Community Technology Preview 5 (CTP5). А вольность… Не обессудьте, но перевод недалеко ушел от Google Translate по качеству и стилистике.
Эта статься будет являться введением в разработку с использованием стратегии Code First («Сначала код»). Code First позволяет определить модель с использованием C# или VB.Net классов, при необходимости дополнительные настройки могут быть выполнены с помощью атрибутов для классов и свойств или с помощью Fluent API. Ваша модель может быть использована для создания схемы базы данных или для сопоставления с существующей базой данных.
Привязка к существующей базе данных
В EF 4.1, начиная с CTP5, мы убрали необходимость выполнить дополнительную настройку при сопоставлении с существующей базой данных. Если Code First обнаруживает, что он указывает на существующую схему базы данных, то он не создает ее, а «доверится» вам и попытается использовать эту схему. Самый простой способ привязать Code First к существующей базе данных, это добавить в App / Web.config строку соединения с таким же именем, как производного DbContext, например:
<connectionStrings>
<add
name="MyProductContext"
providerName="System.Data.SqlClient"
connectionString="Server=.\SQLEXPRESS;Database=Products;Trusted_Connection=true;"/>
</connectionStrings>
Это пошаговое руководство собирается продемонстрировать Code First с генерацией схемы базы данных, но те же самые принципы применяются к отображению на существующую базу данных, за исключением пункта «8. Настройка стратегии инициализации», который не относится к существующим базам данных.
1. Создаем приложение
Для простоты мы создадим консольное приложение, которое использует Code First для доступа к данным:
- Запустите Visual Studio 2010
- File -> New -> Project…
- Выберите “Windows” в меню слева и “Console Application” в основном окне
- Введите “CodeFirstSample” в качестве наименования проекта
- Нажмите “OK”
2. Создаем Модель
Давайте создадим очень простую модель с использованием классов. Я просто определю их в файле Program.cs, но в реальном приложении вы бы выделили ваши классы в отдельные файлы и, возможно, в отдельный проект.
Ниже определения класса Program в Program.cs я добавлю определения следующих двух классов:
public class Category
{
public string CategoryId { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
public virtual Category Category { get; set; }
}
3. Создаем Контекст
Самый простой способ начать использовать классы доступа к данным – определить контекст, который является производным от System.Data.Entity.DbContext и предоставляет набор DbSet<TEntity> для каждого класса в моей модели.
С помощью менеджера пакетов NuGet добавьте в проект необходимые библиотеки EntityFramework 4.1
- Project -> Add Library Package Reference …
- Найдите пакет “EntityFramework ”
- Выберите “EntityFramework” из списка
- Нажмите “Install”
Добавьте ссылку на System.Data.Entity в Program.cs
using System.Data.Entity;
Добавить производный контекст ниже существующих классов, которые мы уже определили
public class ProductContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
Это весь код, который необходимо написать, чтобы начать хранить и извлекать данные. Очевидно, что довольно много всего происходит «за кулисами», и мы еще рассмотрим это в соответствующий момент, но сначала давайте увидим наш код в действии.
5. Чтение и запись данных
Я заполнил метод Main в моей программе как показано ниже:
class Program
{
static void Main(string[] args)
{
using (var db = new ProductContext())
{
// Add a food category
var food = new Category { CategoryId = "FOOD", Name = "Foods" };
db.Categories.Add(food);
int recordsAffected = db.SaveChanges();
Console.WriteLine(
"Saved {0} entities to the database, press any key to exit.",
recordsAffected);
Console.ReadKey();
}
}
}
Теперь вы можете запустить приложение и увидеть, что добавлена новая категория.
Где мои данные?
DbContext по умолчанию создаст базу данных для вас на сервере localhost\SQLEXPRESS. База данных будет названа полным именем вашего производного контекста, в нашем случае это будет “CodeFirstSample.ProductContext”. Мы увидим пути этого поведения далее в этом обзоре.
Определение Модели
DbContext узнает, какие классы включать в модель, полагаясь на свойства DbSet, которые мы определили. Затем он использует соглашения (conventions) по умолчанию Code First для нахождения первичных ключей, внешних ключей и т.д. Полный набор соглашений реализованных в EF4.1 подробно рассматриваются в статье Соглашения (conventions) для Code First в Entity Framework 4.1.
6. Еще про чтение и запись данных
Давайте расширим программу, которую мы только что написали, чтобы показать немного больше функциональности. Мы собираемся воспользоваться методом Find класса DbSet, чтобы найти сущность по ее первичному ключу. Если совпадений не найдено, то Find вернет NULL. Мы также используем LINQ для запроса все продукты в категории «FOOD» в алфавитном порядке по названию. Запрос использует существующий провайдер LINQ to Entities, поэтому поддерживает те же запросы, которые возможны с ObjectSet / ObjectQuery в EF4.
Я заменил код метода Main, написанный ранее, следующим:
class Program
{
static void Main(string[] args)
{
using (var db = new ProductContext())
{
// Use Find to locate the Food category
var food = db.Categories.Find("FOOD");
if (food == null)
{
food = new Category { CategoryId = "FOOD", Name = "Foods" };
db.Categories.Add(food);
}
// Create a new Food product
Console.Write("Please enter a name for a new food: ");
var productName = Console.ReadLine();
var product = new Product { Name = productName, Category = food };
db.Products.Add(product);
int recordsAffected = db.SaveChanges();
Console.WriteLine(
"Saved {0} entities to the database.",
recordsAffected);
// Query for all Food products using LINQ
var allFoods = from p in db.Products
where p.CategoryId == "FOOD"
orderby p.Name
select p;
Console.WriteLine("All foods in database:");
foreach (var item in allFoods)
{
Console.WriteLine(" - {0}", item.Name);
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
7. Изменение имени базы данных
Если вы хотите изменить имя базы данных, которая создается для вас, один из способов настроить это - изменить конструктор DbContext, указав в нем новое имя базы данных.
Скажем, если мы хотим изменить имя базы данных на "MyProductDatabase", мы можем добавить конструктор по умолчанию в наш производный контекст, который «доведет» это название до DbContext:
public class ProductContext : DbContext
{
public ProductContext()
: base("MyProductDatabase")
{ }
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
Другие пути смены базы данных
Есть несколько других путей, как определить, к какой базе данных нужно подключаться. Позже мы сделаем более детальный обзор в отдельной статье
-
App.config Connection String
Создайте строку соединения в файле App.Config с именем, совпадающим с именем вашего контекста. -
DbConnection
Имеется конструктор DbContext, который принимает в качестве параметра DbConnection. -
Replace the Default Convention
Соглашение (convention), используемое для нахождения базы данных на основе имени контекста, содержится в настройке AppDomain, которую вы можете изменить через статическое свойство System.Data.Entity.Database.DbDatabase.DefaultConnectionFactory.
8. Стратегия настройки и инициализации
В следующем разделе мы собираемся начать изменять нашу модель, что в свою очередь означает необходимость изменения схемы базы данных. В настоящее время нет решения "из коробки", как развивать существующую схему. Эволюция базы данных – это то, над чем мы сейчас работаем, и пример направления, в котором мы движемся, приводится в недавнем сообщении в блоге проекта.
Существует, однако, возможность для запуска некоторой логики инициализации базы данных при первом использовании контекста в AppDomain. Это удобно, если вы хотите вставить данные для теста, но это также полезно для повторного создания базы данных, если модель изменилось. В EF 4.1 мы включили пару стратегий, которые вы можете подключить, но также вы можете написать собственные.
В этом обзоре мы просто хотим, чтобы при изменении модели база данных удалялась и заново создавалась, поэтому в верхней части метода Main в классе Proпram я добавил следующий код
Database.SetInitializer<ProductContext>(
new DropCreateDatabaseIfModelChanges<ProductContext>());
9. Аннотация данных
До сих пор мы создавали модель EF с использованием ее соглашений по умолчанию, но настанет время, когда наши классы перестанут следовать соглашениям, и мы должны быть в состоянии выполнить дополнительные настройки. Есть два варианта для этого, мы рассмотрим аннотацию данных в этом разделе, а затем и Code First Fluent API в отдельной статье.
public class Supplier
{
public string SupplierCode { get; set; }
public string Name { get; set; }
}
Также мы должны добавить соответствующий набор в наш производный контекст
public class ProductContext : DbContext
{
public ProductContext()
: base("MyProductDatabase")
{ }
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Supplier> Suppliers { get; set; }
}
Теперь, если мы запустим наше приложение, мы получим ошибку InvalidOperationException с сообщением «EntityType 'Supplier' has no key defined. Define the key for this EntityType.» потому что EF не знает способа определения того, что SupplierCode является первичным ключом для Supplier.
Добавим ссылку на System.ComponentModel.DataAnnotations в Program.cs:
using System.ComponentModel.DataAnnotations;
Теперь мы можем анонсировать свойство SupplierCode как первичный ключ сущности:
public class Supplier
{
[Key]
public string SupplierCode { get; set; }
public string Name { get; set; }
}
Полный список аннотаций, поддерживаемый в EF 4.1:
- KeyAttribute
- StringLengthAttribute
- MaxLengthAttribute
- ConcurrencyCheckAttribute
- RequiredAttribute
- TimestampAttribute
- ComplexTypeAttribute
-
ColumnAttribute
Атрибут свойства для указания имени столбца, ординарного типа и типа данных -
TableAttribute
Атрибут класса для указания имени таблицы и схемы -
InversePropertyAttribute
Атрибут свойства навигации для указания свойства, которое представляет другой конец связи -
ForeignKeyAttribute
Placed on a navigation property to specify the property that represents the foreign key of the relationship -
DatabaseGeneratedAttribute
Атрибут свойства для указания того, как база данных будет вычислять значение поля (Identity, Computed or None) -
NotMappedAttribute
Атрибут свойства или класса для его исключения из базы данных
10. Проверка данных
В EF 4.1 мы включили новую функцию, которая позволяет проверять данные с помощью аннотации данных перед попыткой их сохранения в базе данных
Добавим атрибуты, которые определяют, что Supplier.Name должно быть длиной от 5 до 20 символов:
public class Supplier
{
[Key]
public string SupplierCode { get; set; }
[MinLength(5)]
[MaxLength(20)]
public string Name { get; set; }
}
Добавим ссылку на System.Data.Entity.Validation в Program.cs:
using System.Data.Entity.Validation;
Давайте изменим метод Main, чтобы вставить некоторые неверные данные, отлавливать все исключения и отображать информацию об ошибках:
class Program
{
static void Main(string[] args)
{
DbDatabase.SetInitializer<ProductContext>(
new DropCreateDatabaseIfModelChanges<ProductContext>());
using (var db = new ProductContext())
{
var supplier = new Supplier { Name = "123" };
db.Suppliers.Add(supplier);
try
{
db.SaveChanges();
}
catch (DbEntityValidationException ex)
{
foreach (var failure in ex.EntityValidationErrors)
{
Console.WriteLine(
"{0} failed validation",
failure.Entry.Entity.GetType());
foreach (var error in failure.ValidationErrors)
{
Console.WriteLine(
"- {0} : {1}",
error.PropertyName,
error.ErrorMessage);
}
}
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
Если мы запустим наше приложение, мы увидим следующее:
- Name : The field Name must be a string or array type with a minimum length of '5'.
Press any key to exit.
Резюме
В этом обзоре мы рассмотрели разработку с использованием стратегии Code First из EF 4.1. Мы рассмотрели определение и настройку модели, хранение и поиск данных, настройку подключения к базе данных, обновление схемы базы данных при развитии нашей модели и проверку данных перед их записью в базу данных.
Fluent API
В дополнение к аннотации данных Code First также предоставляет Fluent API для конфигурации, который рассматривается в отдельной статье.
Rowan Miller
Program Manager
ADO.NET Entity Framework
