Entity Framework - аннотации к данным

DataAnnotations используется для настройки классов, которые выделяют наиболее часто используемые конфигурации. Аннотации данных также понимаются рядом приложений .NET, таких как ASP.NET MVC, что позволяет этим приложениям использовать одни и те же аннотации для проверок на стороне клиента. Атрибуты DataAnnotation переопределяют соглашения CodeFirst по умолчанию.

System.ComponentModel.DataAnnotations включает следующие атрибуты, которые влияют на допустимость значений NULL или размер столбца.

  • Key
  • Timestamp
  • ConcurrencyCheck
  • Required
  • MinLength
  • MaxLength
  • StringLength

System.ComponentModel.DataAnnotations.Schema пространство имен включает следующие атрибуты, которые влияют на схему базы данных.

  • Table
  • Column
  • Index
  • ForeignKey
  • NotMapped
  • InverseProperty

Ключ

Entity Framework полагается на то, что каждая сущность имеет значение ключа, которое она использует для отслеживания сущностей. Одно из соглашений, от которого зависит Code First, - это то, как оно подразумевает, какое свойство является ключом в каждом из классов Code First.

  • Соглашение заключается в поиске свойства с именем «Id» или свойства, которое объединяет имя класса и «Id», например «StudentId».

  • Свойство будет сопоставлено со столбцом первичного ключа в базе данных.

  • Классы Student, Course и Enrollment следуют этому соглашению.

Теперь предположим, что класс Student использовал имя StdntID вместо ID. Когда Code First не находит свойство, соответствующее этому соглашению, он генерирует исключение из-за требования Entity Framework о том, что у вас должно быть ключевое свойство. Вы можете использовать ключевую аннотацию, чтобы указать, какое свойство будет использоваться в качестве EntityKey.

Давайте посмотрим на следующий код класса Student, который содержит StdntID, но не соответствует соглашению Code First по умолчанию. Чтобы справиться с этим, добавлен атрибут Key, который сделает его первичным ключом.

public class Student {

   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Когда вы запустите приложение и заглянете в свою базу данных в SQL Server Explorer, вы увидите, что первичным ключом теперь является StdntID в таблице «Студенты».

Entity Framework также поддерживает составные ключи. Composite keysтакже являются первичными ключами, состоящими из более чем одного свойства. Например, у вас есть класс DrivingLicense, первичный ключ которого представляет собой комбинацию LicenseNumber и IssuingCountry.

public class DrivingLicense {

   [Key, Column(Order = 1)]
   public int LicenseNumber { get; set; }
   [Key, Column(Order = 2)]
   public string IssuingCountry { get; set; }
   public DateTime Issued { get; set; }
   public DateTime Expires { get; set; }
}

Когда у вас есть составные ключи, Entity Framework требует, чтобы вы определяли порядок ключевых свойств. Вы можете сделать это, используя аннотацию столбца, чтобы указать порядок.

Отметка времени

Code First будет обрабатывать свойства Timestamp так же, как свойства ConcurrencyCheck, но также будет гарантировать, что поле базы данных, которое сначала создает код, не допускает значения NULL.

  • Для проверки параллелизма чаще используются поля rowversion или timestamp.

  • Вместо использования аннотации ConcurrencyCheck вы можете использовать более конкретную аннотацию TimeStamp, если типом свойства является байтовый массив.

  • У вас может быть только одно свойство timestamp в данном классе.

Давайте посмотрим на простой пример, добавив свойство TimeStamp в класс Course -

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp]
   public byte[] TStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Как вы можете видеть в приведенном выше примере, атрибут Timestamp применяется к свойству Byte [] класса Course. Итак, Code First создаст столбец с отметкой времени TStampв таблице курсов.

ConcurrencyCheck

Аннотация ConcurrencyCheck позволяет пометить одно или несколько свойств, которые будут использоваться для проверки параллелизма в базе данных, когда пользователь редактирует или удаляет объект. Если вы работали с конструктором EF, это согласуется с установкой для свойства ConcurrencyMode значения Fixed.

Давайте посмотрим на простой пример того, как работает ConcurrencyCheck, добавив его в свойство Title в классе Course.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   public string Title { get; set; }
   public int Credits { get; set; }
   [Timestamp, DataType("timestamp")]
   public byte[] TimeStamp { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

В приведенном выше классе Course атрибут ConcurrencyCheck применяется к существующему свойству Title. Теперь Code First будет включать столбец Title в команду обновления для проверки оптимистичного параллелизма, как показано в следующем коде.

exec sp_executesql N'UPDATE [dbo].[Courses]
   SET [Title] = @0
   WHERE (([CourseID] = @1) AND ([Title] = @2))
   ',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus'
go

Обязательная аннотация

Аннотация Required сообщает EF о том, что требуется определенное свойство. Давайте посмотрим на следующий класс Student, в котором обязательный идентификатор добавлен в свойство FirstMidName. Обязательный атрибут заставит EF гарантировать, что в свойстве есть данные.

public class Student {

   [Key]
   public int StdntID { get; set; }

   [Required]
   public string LastName { get; set; }

   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Как видно из приведенного выше примера, атрибут Required применяется к FirstMidName и LastName. Итак, Code First создаст столбцы NOT NULL FirstMidName и LastName в таблице «Студенты», как показано на следующем изображении.

Максимальная длина

Атрибут MaxLength позволяет указать дополнительные проверки свойств. Его можно применить к свойству строкового или массива типа предметного класса. EF Code First установит размер столбца, как указано в атрибуте MaxLength.

Давайте посмотрим на следующий класс Course, в котором атрибут MaxLength (24) применяется к свойству Title.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Когда вы запустите указанное выше приложение, Code First создаст заголовок столбца nvarchar (24) в таблице CourseId, как показано на следующем изображении.

Когда пользователь устанавливает заголовок, содержащий более 24 символов, EF выдаст EntityValidationError.

Миндлина

Атрибут MinLength также позволяет указывать дополнительные проверки свойств, как вы это делали с MaxLength. Атрибут MinLength также можно использовать с атрибутом MaxLength, как показано в следующем коде.

public class Course {

   public int CourseID { get; set; }
   [ConcurrencyCheck]
   [MaxLength(24) , MinLength(5)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

EF выдаст EntityValidationError, если вы установите значение свойства Title меньше указанной длины в атрибуте MinLength или больше указанной длины в атрибуте MaxLength.

StringLength

StringLength также позволяет указать дополнительные проверки свойств, например MaxLength. Единственное отличие состоит в том, что атрибут StringLength может применяться только к свойству строкового типа классов домена.

public class Course {

   public int CourseID { get; set; }
   [StringLength (24)]
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Entity Framework также проверяет значение свойства для атрибута StringLength. Если пользователь устанавливает заголовок, содержащий более 24 символов, EF выдаст EntityValidationError.

Стол

Соглашение по умолчанию Code First создает имя таблицы, аналогичное имени класса. Если вы разрешаете Code First создать базу данных, а также хотите изменить имя создаваемых таблиц. Тогда -

  • Вы можете использовать Code First с существующей базой данных. Но не всегда имена классов совпадают с именами таблиц в вашей базе данных.

  • Атрибут таблицы отменяет это соглашение по умолчанию.

  • EF Code First создаст таблицу с указанным именем в атрибуте Table для заданного класса домена.

Давайте посмотрим на следующий пример, в котором класс называется Student, и по соглашению Code First предполагает, что он будет отображаться в таблицу с именем Student. Если это не так, вы можете указать имя таблицы с помощью атрибута Table, как показано в следующем коде.

[Table("StudentsInfo")]
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Теперь вы можете видеть, что атрибут Table определяет таблицу как StudentsInfo. Когда таблица будет сгенерирована, вы увидите имя таблицы StudentInfo, как показано на следующем изображении.

Вы не можете только указать имя таблицы, но вы также можете указать схему для таблицы с помощью атрибута Table, как показано в следующем коде.

[Table("StudentsInfo", Schema = "Admin")] 
public class Student {

   [Key]
   public int StdntID { get; set; }
   [Required]
   public string LastName { get; set; }
   [Required]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

В приведенном выше примере вы можете видеть, что таблица указана с помощью схемы администратора. Теперь Code First создаст таблицу StudentsInfo в схеме администратора, как показано на следующем изображении.

Столбец

Он также совпадает с атрибутом Table, но атрибут Table переопределяет поведение таблицы, а атрибут Column переопределяет поведение столбца. В соглашении Code First по умолчанию создается имя столбца, аналогичное имени свойства. Если вы позволяете Code First создать базу данных, а также хотите изменить имя столбцов в ваших таблицах. Тогда -

  • Атрибут столбца переопределяет соглашение по умолчанию.

  • EF Code First создаст столбец с указанным именем в атрибуте Column для данного свойства.

Давайте посмотрим на следующий пример, в котором свойство называется FirstMidName, и по соглашению Code First предполагает, что оно будет отображаться в столбец с именем FirstMidName.

Если это не так, вы можете указать имя столбца с атрибутом Column, как показано в следующем коде.

public class Student {

   public int ID { get; set; }
   public string LastName { get; set; }
   [Column("FirstName")]
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Вы можете видеть, что атрибут Column определяет столбец как FirstName. Когда таблица будет сгенерирована, вы увидите имя столбца FirstName, как показано на следующем изображении.

Индекс

Атрибут Index был введен в Entity Framework 6.1. Если вы используете более раннюю версию, информация в этом разделе не применяется.

  • Вы можете создать индекс для одного или нескольких столбцов с помощью IndexAttribute.

  • Добавление атрибута к одному или нескольким свойствам приведет к тому, что EF создаст соответствующий индекс в базе данных при создании базы данных.

  • В большинстве случаев индексы делают поиск данных более быстрым и эффективным. Однако перегрузка таблицы или представления индексами может неприятно повлиять на производительность других операций, таких как вставки или обновления.

  • Индексирование - это новая функция в Entity Framework, с помощью которой вы можете повысить производительность своего приложения Code First, сократив время, необходимое для запроса данных из базы данных.

  • Вы можете добавлять индексы в свою базу данных с помощью атрибута Index и переопределять параметры по умолчанию Unique и Clustered, чтобы получить индекс, наиболее подходящий для вашего сценария.

  • По умолчанию индекс будет называться IX_ <название свойства>.

Давайте посмотрим на следующий код, в котором атрибут Index добавлен в класс Course для Credits.

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]
   public int Credits { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Вы можете видеть, что атрибут Index применяется к свойству Credits. Когда таблица будет сгенерирована, вы увидите IX_Credits в индексах.

По умолчанию индексы неуникальны, но вы можете использовать IsUniqueименованный параметр, чтобы указать, что индекс должен быть уникальным. В следующем примере представлен уникальный индекс, как показано в следующем коде.

public class Course {
   public int CourseID { get; set; }
   [Index(IsUnique = true)]
	
   public string Title { get; set; }
   [Index]
	
   public int Credits { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Внешний ключ

Соглашение Code First позаботится о наиболее распространенных отношениях в вашей модели, но в некоторых случаях ему требуется помощь. Например, при изменении имени ключевого свойства в классе Student возникла проблема с его отношением к классу Enrollment.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

При создании базы данных Code First видит свойство StudentID в классе Enrollment и распознает его в соответствии с соглашением о том, что оно соответствует имени класса плюс «ID», как внешний ключ для класса Student. Однако в классе Student нет свойства StudentID, но это свойство StdntID является классом Student.

Решением для этого является создание свойства навигации в Enrollment и использование ForeignKey DataAnnotation, чтобы помочь Code First понять, как строить отношения между двумя классами, как показано в следующем коде.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
	
   public Grade? Grade { get; set; }
   public virtual Course Course { get; set; }
   [ForeignKey("StudentID")]
	
   public virtual Student Student { get; set; }
}

Теперь вы можете видеть, что атрибут ForeignKey применяется к свойству навигации.

NotMapped

По соглашениям Code First по умолчанию каждое свойство поддерживаемого типа данных, которое включает методы получения и установки, представлено в базе данных. Но в ваших приложениях это не всегда так. Атрибут NotMapped отменяет это соглашение по умолчанию. Например, у вас может быть свойство в классе Student, такое как имя отца, но его не нужно хранить. Вы можете применить атрибут NotMapped к свойству FatherName, столбец которого вы не хотите создавать в базе данных, как показано в следующем коде.

public class Student {
   [Key]
   public int StdntID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
	
   public DateTime EnrollmentDate { get; set; }
   [NotMapped]

   public int FatherName { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Вы можете видеть, что атрибут NotMapped применяется к свойству FatherName. Когда таблица будет сгенерирована, вы увидите, что столбец «Имя отца» не будет создан в базе данных, но он присутствует в классе Student.

Code First не будет создавать столбец для свойства, которое не имеет ни геттеров, ни сеттеров, как показано в следующем примере свойств Address и Age класса Student.

InverseProperty

InverseProperty используется, когда у вас есть несколько отношений между классами. В классе Enrollment вы можете отслеживать, кто записался на текущий курс и предыдущий курс. Давайте добавим два свойства навигации для класса Enrollment.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
   public int StudentID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course CurrCourse { get; set; }
   public virtual Course PrevCourse { get; set; }
   public virtual Student Student { get; set; }
}

Точно так же вам также необходимо добавить класс Course, на который ссылаются эти свойства. Класс Course имеет свойства перехода к классу Enrollment, который содержит все текущие и предыдущие регистрации.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

Code First создает столбец внешнего ключа {Class Name} _ {Primary Key}, если свойство внешнего ключа не включено в конкретный класс, как показано в приведенных выше классах. Когда база данных будет сгенерирована, вы увидите следующие внешние ключи.

Как видите, Code first не может самостоятельно сопоставить свойства в двух классах. Таблица базы данных для Enrollments должна иметь один внешний ключ для CurrCourse и один для PrevCourse, но Code First создаст четыре свойства внешнего ключа, т. Е.

  • CurrCourse _CourseID
  • PrevCourse _CourseID
  • Course_CourseID и
  • Course_CourseID1

Чтобы исправить эти проблемы, вы можете использовать аннотацию InverseProperty, чтобы указать выравнивание свойств.

public class Course {

   public int CourseID { get; set; }
   public string Title { get; set; }
   [Index]

   public int Credits { get; set; }
   [InverseProperty("CurrCourse")]

   public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
   [InverseProperty("PrevCourse")]

   public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}

Как видите, атрибут InverseProperty применяется в указанном выше классе Course, указывая, к какому ссылочному свойству класса Enrollment он принадлежит. Теперь Code First сгенерирует базу данных и создаст только два столбца внешнего ключа в таблице Enrollments, как показано на следующем изображении.

Мы рекомендуем вам выполнить приведенный выше пример поэтапно для лучшего понимания.