Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Примечание.
Только начиная с EF4.1 — функции, API и т. д., обсуждаемые на этой странице, были внедрены в Entity Framework 4.1. Если вы используете более раннюю версию, некоторые или все эти сведения не применяются.
Содержимое на этой странице адаптировано из статьи, первоначально написанной Джули Лерман (<http://thedatafarm.com>).
Entity Framework Code First позволяет использовать собственные классы домена для представления модели, которую EF использует для выполнения запросов, отслеживания изменений и обновления функций. Code First использует шаблон программирования, называемый "соглашением по конфигурации". Метод Code First предполагает, что ваши классы следуют соглашениям Entity Framework, и в этом случае он автоматически определяет, как выполнять свою работу. Однако если классы не соответствуют этим соглашениям, у вас есть возможность добавлять конфигурации в классы для предоставления EF необходимых сведений.
Code First предоставляет два способа добавления этих конфигураций в классы. Один из них использует простые атрибуты с именем DataAnnotations, а второй — использование API Fluent Code First, который предоставляет способ описания конфигураций императивно в коде.
В этой статье будет сосредоточена на использовании DataAnnotations (в пространстве имен System.ComponentModel.DataAnnotations), чтобы настроить ваши классы; с акцентом на наиболее часто используемые конфигурации. DataAnnotations также понимаются рядом приложений .NET, таких как ASP.NET MVC, что позволяет этим приложениям использовать те же аннотации для проверок на клиентской стороне.
Модель
Я продемонстрирую аннотации данных Code First с помощью простой пары классов: Blog и Post.
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string BloggerName { get; set;}
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public ICollection<Comment> Comments { get; set; }
}
Как есть, классы Blog и Post удобно следуют конвенции code first и не требуют настройки для обеспечения совместимости с EF. Однако можно также использовать аннотации для предоставления дополнительных сведений EF о классах и базе данных, куда они отображаются.
Ключ
Entity Framework использует все сущности, имеющие ключевое значение, используемое для отслеживания сущностей. Одно соглашение Code First — это неявные ключевые свойства; Code First будет искать свойство с именем "Id", или сочетание имени класса и идентификатора, например "BlogId". Это свойство сопоставляется со столбцом первичного ключа в базе данных.
Классы Blog и Post следуют этому соглашению. Что делать, если они этого не сделали? Что делать, если блог использовал имя PrimaryTrackingKey вместо этого или даже foo? Если Code First не находит свойство, соответствующее этому соглашению, он вызовет исключение из-за требования Entity Framework о необходимости иметь ключевое свойство. С помощью ключевой заметки можно указать, какое свойство следует использовать в качестве EntityKey.
public class Blog
{
[Key]
public int PrimaryTrackingKey { get; set; }
public string Title { get; set; }
public string BloggerName { get; set;}
public virtual ICollection<Post> Posts { get; set; }
}
Если вы используете функцию генерации базы данных Code First, таблица Blog будет иметь столбец первичного ключа с именем PrimaryTrackingKey, который также определяется как Identity по умолчанию.
Составные ключи
Entity Framework поддерживает составные ключи — первичные ключи, состоящие из нескольких свойств. Например, у вас может быть класс Passport, первичный ключ которого — сочетание PassportNumber и IssuingCountry.
public class Passport
{
[Key]
public int PassportNumber { get; set; }
[Key]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Попытка использовать приведенный выше класс в модели EF приведет к: InvalidOperationException
Не удалось определить составной порядок первичного ключа для типа Passport. Используйте метод ColumnAttribute или HasKey, чтобы указать порядок составных первичных ключей.
Чтобы использовать составные ключи, Entity Framework требует определить порядок свойств ключа. Это можно сделать с помощью аннотации "Столбец" для указания порядка.
Примечание.
Значение порядка является относительным (а не на основе индекса), поэтому любые значения можно использовать. Например, 100 и 200 будут допустимы вместо 1 и 2.
public class Passport
{
[Key]
[Column(Order=1)]
public int PassportNumber { get; set; }
[Key]
[Column(Order = 2)]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Если у вас есть сущности с составными внешними ключами, необходимо указать то же упорядочение столбцов, которое использовалось для соответствующих свойств первичного ключа.
Только относительное упорядочение в свойствах внешнего ключа должно совпадать, точные значения, назначенные Order , не должны совпадать. Например, в следующем классе можно использовать 3 и 4 вместо 1 и 2.
public class PassportStamp
{
[Key]
public int StampId { get; set; }
public DateTime Stamped { get; set; }
public string StampingCountry { get; set; }
[ForeignKey("Passport")]
[Column(Order = 1)]
public int PassportNumber { get; set; }
[ForeignKey("Passport")]
[Column(Order = 2)]
public string IssuingCountry { get; set; }
public Passport Passport { get; set; }
}
Обязательное поле
Заметка Required сообщает EF, что требуется определенное свойство.
Добавление атрибута Required к свойству Title заставит EF (и MVC) убедиться, что это свойство содержит данные.
[Required]
public string Title { get; set; }
Без дополнительных изменений кода или разметки в приложении приложение MVC будет выполнять проверку на стороне клиента, даже динамически создавая сообщение с помощью имен свойств и заметок.
Атрибут [Required] также влияет на созданную базу данных, делая сопоставленное свойство не допускающим значения NULL. Обратите внимание, что поле «Title» изменилось на «не пустое».
Примечание.
В некоторых случаях столбец в базе данных может быть не допускаемым значением NULL, даже если это свойство является обязательным. Например, при использовании стратегии наследования TPH данные для нескольких типов хранятся в одной таблице. Если производный тип включает обязательное свойство, столбец нельзя сделать не допускающим значения NULL, поскольку не все типы в иерархии будут иметь это свойство.
MaxLength и MinLength
Атрибуты MaxLength и MinLength позволяют указывать дополнительные проверки свойств, так же как и при использовании Required.
Вот имя блоггера с требованиями к длине. В примере также показано, как объединять атрибуты.
[MaxLength(10),MinLength(5)]
public string BloggerName { get; set; }
Заметка MaxLength повлияет на базу данных, задав длину свойства 10.
Заметка на стороне клиента MVC и заметка на стороне сервера EF 4.1 будут учитывать эту проверку, снова динамически создавая сообщение об ошибке: "Поле BlogName должно быть строкой или типом массива с максимальной длиной "10". Это сообщение немного долго. Многие заметки позволяют указать сообщение об ошибке с атрибутом ErrorMessage.
[MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
public string BloggerName { get; set; }
Вы также можете указать ErrorMessage в аннотации Required.
NotMapped
Первое соглашение кода определяет, что каждое свойство, являющееся поддерживаемым типом данных, представлено в базе данных. Однако в ваших приложениях это может быть не так. Например, у вас может быть свойство в классе Блога, которое создает код на основе полей Title и BlogName. Это свойство можно создать динамически и не нужно хранить. Вы можете пометить любые свойства, которые не сопоставляются с базой данных с помощью заметки NotMapped, например этого свойства BlogCode.
[NotMapped]
public string BlogCode
{
get
{
return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
}
}
КомплексныйТип
Не является редкостью описывать сущности домена в наборе классов, а затем наслоить эти классы, чтобы полностью описать сущность. Например, в модель можно добавить класс с именем BlogDetails.
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
Обратите внимание, что BlogDetails у него нет какого-либо типа свойства ключа. В управляемом доменом дизайне BlogDetails называется объектом значения. Entity Framework относится к объектам значений как сложным типам. Сложные типы нельзя отслеживать самостоятельно.
Однако как свойство в Blog классе BlogDetails будет отслеживаться как часть Blog объекта. Чтобы код сначала распознал это, необходимо пометить BlogDetails класс как класс ComplexType.
[ComplexType]
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
Теперь вы можете добавить свойство в класс Blog, чтобы представить BlogDetails для этого блога.
public BlogDetails BlogDetail { get; set; }
В базе данных таблица Blog будет содержать все свойства блога, а также свойства, находящиеся в его свойстве BlogDetail. По умолчанию каждый из них предшествует имени сложного типа BlogDetail.
ConcurrencyCheck
Заметка ConcurrencyCheck позволяет пометить одно или несколько свойств для проверки параллелизма в базе данных при изменении или удалении сущности пользователем. Если вы работали с конструктором EF, это соответствует изменению свойства с ConcurrencyMode на Fixed.
Давайте посмотрим, как ConcurrencyCheck работает, добавив его в BloggerName свойство.
[ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
public string BloggerName { get; set; }
При вызове SaveChanges из-за аннотации ConcurrencyCheck в поле BloggerName исходное значение этого свойства используется для обновления. Команда попытается найти правильную строку, отфильтровав не только значение ключа, но и исходное значение BloggerName. Ниже приведены критически важные части команды UPDATE, отправляемой в базу данных, где вы увидите, что команда обновит строку, в которой PrimaryTrackingKey равен 1 и BloggerName равно "Джули", что было исходным значением при извлечении этого блога из базы данных.
where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
@4=1,@5=N'Julie'
Если кто-то изменил имя блогера для этого блога одновременно, это обновление приведет к ошибке, и вы получите DbUpdateConcurrencyException, который вам потребуется обработать.
TimeStamp
Чаще всего используются поля rowversion или timestamp для проверки параллелизма. Но вместо использования ConcurrencyCheck заметки можно использовать более конкретную TimeStamp заметку, если тип свойства является массивом байтов. Сначала код будет обрабатывать Timestamp свойства так же, как ConcurrencyCheck свойства, но также гарантирует, что поле базы данных, которое сначала создает код, не является пустым. В данном классе можно использовать только одно свойство метки времени.
Добавление следующего свойства в класс Блога:
[Timestamp]
public Byte[] TimeStamp { get; set; }
Код сначала создает столбец с меткой времени, который не допускает значения NULL, в таблице базы данных.
Таблица и столбец
Если вы позволяете коду сначала создать базу данных, может потребоваться изменить имя таблиц и столбцов, которые он создает. Вы также можете использовать code First с существующей базой данных. Но это не всегда так, что имена классов и свойств в домене соответствуют именам таблиц и столбцов в базе данных.
Мой класс называется Blog и по соглашению код предполагает, что он сопоставляется с таблицей с именем Blogs. Если это не так, можно указать имя таблицы с атрибутом Table . Например, заметка указывает, что имя таблицы — InternalBlogs.
[Table("InternalBlogs")]
public class Blog
Аннотация Column является более продвинутой в определении атрибутов сопоставленного столбца. Можно указать имя, тип данных или даже порядок отображения столбца в таблице. Ниже приведен пример атрибута Column .
[Column("BlogDescription", TypeName="ntext")]
public String Description {get;set;}
Не путайте атрибут column TypeName с dataType DataAnnotation. DataType — это аннотация, используемая для пользовательского интерфейса и игнорируемая в Code First.
Ниже приведена таблица после повторного восстановления. Имя таблицы изменилось на InternalBlogs, и столбец Description из сложного типа теперь BlogDescription. Так как имя было указано в аннотации, Code First не будет использовать соглашение об именовании, начинающее имя столбца с имени сложного типа.
Генерируется базой данных
Важной особенностью базы данных является возможность иметь вычисляемые свойства. Если вы сопоставляете классы Code First с таблицами, содержащими вычисляемые столбцы, не хотите, чтобы Entity Framework пыталась обновить эти столбцы. Но вы хотите, чтобы EF возвращал эти значения из базы данных после вставки или обновления данных. С помощью аннотации DatabaseGenerated можно пометить эти свойства в классе вместе с перечислением Computed. Другие перечисления — это None и Identity.
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated { get; set; }
Вы можете использовать базу данных, созданную на столбцах с типом данных байт или временная метка, если она создается с помощью подхода "сначала код". В противном случае следует использовать это только при работе с уже существующими базами данных, так как подход "сначала код" не сможет определить формулу для вычисляемого столбца.
Выше описано, что по умолчанию свойство ключа, которое является целым числом, станет ключом идентификации в базе данных. Это будет то же самое, что установить DatabaseGenerated на DatabaseGeneratedOption.Identity. Если вы не хотите, чтобы он был ключом удостоверения, можно задать для него значение DatabaseGeneratedOption.None.
Индекс
Примечание.
Начиная с EF6.1 — Index атрибут был введён в Entity Framework 6.1. Если вы используете более раннюю версию, сведения в этом разделе не применяются.
Индекс можно создать на одном или нескольких столбцах с помощью IndexAttribute. Добавление атрибута к одному или нескольким свойствам приведет к созданию соответствующего индекса в базе данных при создании базы данных или генерации соответствующих вызовов CreateIndex, если вы используете Code First Migrations.
Например, следующий код приведет к созданию индекса в Rating столбце Posts таблицы в базе данных.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Index]
public int Rating { get; set; }
public int BlogId { get; set; }
}
По умолчанию индекс будет называться IX_<property name> (IX_Rating в приведенном выше примере). Кроме того, можно указать имя индекса. В следующем примере указывается, что индекс должен быть назван PostRatingIndex.
[Index("PostRatingIndex")]
public int Rating { get; set; }
По умолчанию индексы являются не уникальными, но можно использовать IsUnique именованный параметр, чтобы указать, что индекс должен быть уникальным. В следующем примере представлен уникальный индекс имени входа User.
public class User
{
public int UserId { get; set; }
[Index(IsUnique = true)]
[StringLength(200)]
public string Username { get; set; }
public string DisplayName { get; set; }
}
Индексы с несколькими столбцами
Индексы, охватывающие несколько столбцов, задаются с помощью одного имени в нескольких заметках индекса для заданной таблицы. При создании индексов с несколькими столбцами необходимо указать порядок столбцов в индексе. Например, следующий код создает многоколонный индекс на Rating и BlogId, называемый IX_BlogIdAndRating.
BlogId — первый столбец в индексе и Rating является вторым.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Index("IX_BlogIdAndRating", 2)]
public int Rating { get; set; }
[Index("IX_BlogIdAndRating", 1)]
public int BlogId { get; set; }
}
Атрибуты связи: InverseProperty и ForeignKey
Примечание.
Эта страница содержит информацию о настройке связей в вашей модели Code First с использованием аннотаций данных. Общие сведения о отношениях в EF и о том, как получить доступ к данным и управлять ими с помощью связей, см. в разделе "Связи и свойства навигации".*
Конвенция 'code first' будет обрабатывать наиболее распространенные связи в вашей модели, но есть случаи, когда ей нужна помощь.
Изменение имени ключевого свойства в данном Blog классе создало проблему с его связью с Post.
При создании базы данных код сначала видит свойство BlogId в классе Post и распознает его по соглашению об именовании, при котором используется имя класса вкупе с Id, как внешний ключ к классу Blog. Но в классе блога нет BlogId свойства. Решением для этого является создание свойства навигации в Post dataAnnotation и использование ForeignKey DataAnnotation, чтобы сначала понять, как создать связь между двумя классами (с помощью Post.BlogId свойства), а также как указать ограничения в базе данных.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
[ForeignKey("BlogId")]
public Blog Blog { get; set; }
public ICollection<Comment> Comments { get; set; }
}
Ограничение в базе данных показывает связь между InternalBlogs.PrimaryTrackingKey и Posts.BlogId.
Используется InverseProperty при наличии нескольких связей между классами.
В Post классе вы можете отслеживать, кто написал публикацию в блоге, а также кто её редактировал. Ниже приведены два новых свойства навигации для класса Post.
public Person CreatedBy { get; set; }
public Person UpdatedBy { get; set; }
Вам также потребуется добавить класс Person, на который ссылаются эти свойства. Класс Person имеет свойства навигации к Post, одно для всех записей, написанных этим человеком, и одно для всех записей, обновленных этим человеком.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public List<Post> PostsWritten { get; set; }
public List<Post> PostsUpdated { get; set; }
}
Сначала код не может соответствовать свойствам в двух классах самостоятельно. Таблица Posts базы данных должна иметь один внешний ключ для CreatedBy пользователя и один для UpdatedBy пользователя, но сначала код создаст четыре свойства внешнего ключа: Person_Id, Person_Id1, CreatedBy_Id и UpdatedBy_Id.
Чтобы устранить эти проблемы, можно использовать заметку InverseProperty для указания выравнивания свойств.
[InverseProperty("CreatedBy")]
public List<Post> PostsWritten { get; set; }
[InverseProperty("UpdatedBy")]
public List<Post> PostsUpdated { get; set; }
PostsWritten Поскольку свойство в Person знает, что это относится к Post типу, оно будет строить связь с Post.CreatedBy. Аналогичным образом PostsUpdated будет подключен к Post.UpdatedBy. И код сначала не создаст дополнительные внешние ключи.
Итоги
DataAnnotations не только позволяют описывать проверку на стороне клиента и сервера в классах Code First, но и позволяют улучшать и даже исправлять предположения, которые Code First делает о классах на основе своих соглашений. С помощью dataAnnotations можно не только управлять созданием схемы базы данных, но и сопоставить первые классы кода с предварительно существующей базой данных.
Хотя они очень гибкие, помните, что DataAnnotations предоставляют только наиболее часто необходимые изменения конфигурации, которые можно внести в первые классы кода. Чтобы настроить классы для некоторых крайних случаев, следует обратиться к альтернативному механизму настройки Fluent API технологии Code First.