Настройка нереляционных свойств в EF Core

Внимание

Данный материал является частью цикла статей «Основы Entity Framework Core». Не забудьте посмотреть другие статьи по этой теме :-)

  1. Начало работы с Entity Framework Core в ASP.NET Core - модели, DbContext, конфигурация
  2. Настройка нереляционных свойств в EF Core
  3. Миграции в Entity Framework Core
  4. Entity Framework Core – организация связей между моделями: соглашения, аннотации данных и Fluent API
  5. Запросы к базе данных в Entity Framework Core
  6. Изменение данных с помощью Entity Framework Core

Конфигурация EF Core по соглашению

Конфигурация по соглашению означает, что EF Core будет следовать набору простых правил для типов и имен свойств и соответствующим образом настраивать базу данных. Этот подход можно обойти, используя аннотации данных или подход Fluent API.

Как мы уже упоминали, EF Core настраивает поле первичного ключа из нашего класса модели Student, следуя соглашению об именах. Это означает, что свойство будет преобразовано как первичный ключ в базе данных, если у него есть имя свойства «Id» или комбинация <Name of a class> + Id (как в случае с нашим свойством StudentId):

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

При использовании конфигурации по соглашению допустимость значения NULL для столбца зависит от типа свойства из нашего класса модели. Когда EF Core использует конфигурацию по соглашению, он просматривает все общедоступные свойства и сопоставляет их по имени и типу. Таким образом, это означает, что свойство Name может иметь в качестве значения Null(поскольку значение по умолчанию для строкового типа равно null), но Age не может (потому что это тип значения):

Конечно, если мы хотим, чтобы свойство Age допускало значение NULL в базе данных, мы можем использовать суффикс «?», например, так (int? Age) или мы можем использовать общий Nullable<T>, например (Nullable<int> Age):

Конфигурация EF Core с помощью аннотаций к данным

Аннотации к данным - это особые атрибуты .NET, которые мы используем для проверки и настройки функций базы данных. Есть два соответствующих пространства имен, из которых мы можем получить атрибуты аннотации: System.ComponentModel.DataAnnotations и System.ComponentModel.DataAnnotations.Schema. Атрибуты из первого пространства имен в основном связаны с проверкой свойств, а атрибуты из второго пространства имен связаны с конфигурацией базы данных.

Итак, давайте посмотрим, как работает аннотация к данным.

Начнем с изменения класса Student, добавив несколько атрибутов проверки:

Атрибут Обязательный, мы заявляем, что поле Name не может допускать значения NULL, и с помощью свойства MaxLengh мы ограничиваем длину этого столбца. в базе данных.

Итак, из этого примера мы можем увидеть, как аннотации к данным переопределяют конфигурацию по соглашению. Посмотрим на результат:

Мы можем внести дополнительные изменения в класс Student, добавив атрибуты [Table] и [Column]:

[Table("Student")]
public class Student
{
    [Column("StudentId")]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(50, ErrorMessage = "Длина строки должна быть меньше 50 символов")]
    public string Name { get; set; }

    public int? Age { get; set; }
}

Атрибут таблицы

Как видите, с помощью атрибута [Table] мы направляем EF Core в нужную таблицу или схему для сопоставления в базе данных. Сейчас имя таблицы в базе данных - Student, потому что свойство DbSet<T> названо Student в классе ApplicationContext. Но атрибут [Table] изменит это. Итак, если по какой-либо причине нам нужно изменить имя класса, атрибут [Table] окажется весьма полезным.

Если таблица, с которой мы сопоставляем, принадлежит схеме, отличной от схемы по умолчанию, мы можем использовать атрибут [Table ("TableName", Schema = "SchemaName")], чтобы предоставить информация о требуемой схеме.

Атрибут столбца

Атрибут [Column] предоставляет EF Core информацию о том, какой столбец нужно сопоставить в базе данных. Итак, если мы хотим иметь свойство Id вместо StudentId в нашем классе, но в базе данных, мы все равно хотим иметь StudentId в названии нашего столбца нам в этом помогает атрибут [Column].

Мы также можем указать Порядок и Тип базы данных столбца с помощью этого атрибута [Column ("ColumnName", Order = 1, TypeName = "nvarchar(50)")].

После этих изменений в нашем классе наша таблица будет иметь то же ключевое поле, но другое имя:

Использование подхода Fluent API

Fluent API - это набор методов, которые мы можем использовать в классе ModelBuilder, который доступен в методе OnModelCreating в нашем классе context (ApplicationContext). Этот подход предоставляет большое количество вариантов конфигурации EF Core, которые мы можем использовать при настройке наших сущностей.

Итак, давайте создадим метод OnModelCreating в классе ApplicationContext и добавим ту же конфигурацию, что и в подходе с аннотациями к данным:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Student>()
        .ToTable("Student");
    modelBuilder.Entity<Student>()
        .Property(s => s.Id)
        .HasColumnName("StudentId");
    modelBuilder.Entity<Student>()
        .Property(s => s.Name)
        .IsRequired()
        .HasMaxLength(50);
    modelBuilder.Entity<Student>()
        .Property(s => s.Age)
        .IsRequired(false);
}

Вначале мы выбираем сущность для настройки и с помощью метода Property указываем, на какое свойство мы хотим добавить ограничение. Все остальные методы не требуют пояснений.

OnModelCreating вызывается в первый раз, когда наше приложение создает экземпляр класса ApplicationContext. На данный момент применяются все три подхода. Как видите, мы не использовали какой-либо метод для первичного ключа, но в нашей таблице он, тем не менее, есть из-за соглашения об именах:

Исключение объектов или классов из сопоставления

Мы говорили о том, как Entity Framework Core отображает все свойства в столбцы таблицы. Но иногда у нас могут быть некоторые свойства, которые нам нужны в нашем классе, но мы не хотим, чтобы они были столбцом внутри таблицы. Давайте посмотрим, как это сделать с помощью аннотаций к данным:

public class Student
{
    [Column("StudentId")]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(50, ErrorMessage = "Длина строки должна быть меньше 50 символов")]
    public string Name { get; set; }

    public int? Age { get; set; }

    [NotMapped]
    public int LocalCalculation { get; set; }
}

Атрибут [NotMapped] позволяет нам исключить определенные свойства из сопоставления и, таким образом, избежать создания этого столбца в таблице. Мы также можем исключить класс, если нам нужно:

[NotMapped]
public class NotMappedClass
{
    //аттрибуты
}

Конечно, мы можем сделать то же самое с помощью подхода Fluent API:

modelBuilder.Entity<Student>()
    .Ignore(s => s.LocalCalculation);

modelBuilder.Ignore<NotMappedClass>();

Как видите, если мы игнорируем свойство, тогда метод Ignore привязывается непосредственно к методу Entity, а не к методу Property, как мы делали в предыдущем конфигурация. Но если мы игнорируем класс, тогда метод Ignore вызывается с самим объектом modelBuilder.

Конфигурация PrimaryKey с аннотациями к данным и Fluent API

В одном из предыдущих разделов мы видели, что EF Core автоматически устанавливает первичный ключ в базе данных, используя соглашение об именах. Но соглашение об именах не будет работать, если имя свойства не соответствует соглашению об именах или если у нас есть составной ключ в нашей сущности. В этих ситуациях мы должны использовать либо подход аннотаций к данным, либо Fluent API.

Итак, чтобы настроить свойство PrimaryKey с помощью подхода Data Annotations, мы должны использовать атрибут [Key]:

public class Student
{
    [Key]
    [Column("StudentId")]
    public Guid Id { get; set; }

    [Required]
    [MaxLength(50, ErrorMessage = "Длина строки должна быть меньше 50 символов")]
    public string Name { get; set; }

    public int? Age { get; set; }

    [NotMapped]
    public int LocalCalculation { get; set; }
}

Если мы хотим использовать подход Fluent API, мы должны использовать HasKey

Давайте добавим дополнительное свойство к классу Student, просто для примера:

public Guid AnotherKeyProperty { get; set; }

Теперь мы можем настроить составной ключ:

modelBuilder.Entity<Student>()
     .HasKey(s => new { s.Id, s.AnotherKeyProperty });

Итак, мы используем тот же метод HasKey, но мы создаем анонимный объект, который содержит оба ключевых свойства. Вот результат:

Работа с индексами и значениями по умолчанию

Мы собираемся использовать подход Fluent API для добавления индексов в таблицы, потому что это единственный поддерживаемый способ.

Чтобы добавить индекс к требуемому свойству, мы можем использовать такую ​​инструкцию:

modelBuilder.Entity<Student>()
    .HasIndex(s => s.PropertyName);

Для нескольких столбцов, на которые влияет индекс:

modelBuilder.Entity<Student>()
    .HasIndex(s => new { s.PropertyName1, s.PropertyName2 });

Если мы хотим использовать именованный индекс:

modelBuilder.Entity<Student>()
    .HasIndex(s => s.PropertyName)
    .HasName("index_name");

Добавление уникального ограничения гарантирует, что столбец будет иметь только уникальные значения:

modelBuilder.Entity<Student>()
    .HasIndex(s => s.PropertyName)
    .HasName("index_name")
    .IsUnique();

Кроме того, мы можем настроить наши свойства так, чтобы они имели значения по умолчанию при создании новой строки. Чтобы показать, как работает эта функция, мы собираемся добавить дополнительное свойство в класс Student:

public bool IsRegularStudent { get; set; }

Теперь мы можем настроить его значение по умолчанию через Fluent API:

modelBuilder.Entity<Student>()
    .Property(s => s.IsRegularStudent)
    .HasDefaultValue(true);

Должен быть результат:

Рекомендации по использованию различных подходов к настройке EF Core

Entity Framework Core отлично справляется с настройкой нашей базы данных, используя правила, которые мы предоставляем. Поскольку теперь мы знаем три разных подхода к настройке, может возникнуть некоторая путаница, какой из них использовать.

Итак, вот несколько рекомендаций.

По соглашению

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

Аннотации к данным

Для конфигурации проверки, такой как проверка обязательной или максимальной длины, всегда следует отдавать предпочтение аннотациям к данным, а не подходу Fluent API. И вот почему:

  • Легко увидеть, какое правило проверки связано с каким свойством, поскольку оно размещено прямо над этим свойством и не требует пояснений.
  • Проверки с помощью аннотаций к данным можно использовать во внешнем интерфейсе, потому что, как мы видели в классе Student, мы можем настроить сообщения об ошибках в случае сбоя проверки.
  • Мы хотим использовать эти правила проверки до метода SaveChanges EF Core (мы поговорим об этом в следующих статьях). Такой подход значительно упростит наш код проверки и упростит его сопровождение.

Свободный API

Скажем так, мы должны использовать этот подход для всего остального. Конечно, мы должны использовать этот подход для конфигурации, которую мы не можем сделать иначе, или когда мы хотим скрыть настройку конфигурации от класса модели. Итак, индексы, составные ключи, отношения - это то, что мы должны сохранить в методе OnModelCreating.

Заключение

Итак, мы рассмотрели различные функции конфигурации, которые предоставляет нам EF Core. Конечно, есть дополнительные параметры конфигурации, связанные с аннотациями к данным и Fluent API, и эта серия далеко не закончена, поэтому мы упомянем некоторые из них позже.