Entity Framework - свободный API

Fluent API - это расширенный способ определения конфигурации модели, который охватывает все, что могут делать аннотации данных, в дополнение к более сложной конфигурации, невозможной с аннотациями данных. Аннотации к данным и свободный API можно использовать вместе, но Code First отдает приоритет Fluent API> аннотациям данных> соглашениям по умолчанию.

  • Fluent API - еще один способ настроить классы домена.

  • К API Code First Fluent чаще всего обращаются путем переопределения метода OnModelCreating в производном DbContext.

  • Fluent API предоставляет больше возможностей для настройки, чем DataAnnotations. Fluent API поддерживает следующие типы сопоставлений.

В этой главе мы продолжим рассмотрение простого примера, который содержит классы Student, Course и Enrollment и один класс контекста с именем MyContext, как показано в следующем коде.

using System.Data.Entity; 
using System.Linq; 
using System.Text;
using System.Threading.Tasks;  

namespace EFCodeFirstDemo {

   class Program {
      static void Main(string[] args) {}
   }
   
   public enum Grade {
      A, B, C, D, F
   }

   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 {
      public int ID { get; set; }
      public string LastName { get; set; }
      public string FirstMidName { get; set; }
		
      public DateTime EnrollmentDate { get; set; }
		
      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }

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

   public class MyContext : DbContext {
      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
   }

}

Чтобы получить доступ к Fluent API, вам необходимо переопределить метод OnModelCreating в DbContext. Давайте рассмотрим простой пример, в котором мы переименуем имя столбца в таблице учеников с FirstMidName на FirstName, как показано в следующем коде.

public class MyContext : DbContext {

   protected override void OnModelCreating(DbModelBuilder modelBuilder) {
      modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
      .HasColumnName("FirstName");}

      public virtual DbSet<Course> Courses { get; set; }
      public virtual DbSet<Enrollment> Enrollments { get; set; }
      public virtual DbSet<Student> Students { get; set; }
}

DbModelBuilder используется для отображения классов CLR в схему базы данных. Это основной класс, на котором вы можете настроить все классы своего домена. Этот ориентированный на код подход к созданию модели данных сущности (EDM) известен как Code First.

Fluent API предоставляет ряд важных методов для настройки сущностей и их свойств для переопределения различных соглашений Code First. Ниже приведены некоторые из них.

Sr. No. Название и описание метода
1

ComplexType<TComplexType>

Регистрирует тип как сложный тип в модели и возвращает объект, который можно использовать для настройки сложного типа. Этот метод можно вызывать несколько раз для одного и того же типа для выполнения нескольких строк конфигурации.

2

Entity<TEntityType>

Регистрирует тип сущности как часть модели и возвращает объект, который можно использовать для настройки сущности. Этот метод можно вызывать несколько раз для одной и той же сущности для выполнения нескольких строк конфигурации.

3

HasKey<TKey>

Настраивает свойства первичного ключа для этого типа сущности.

4

HasMany<TTargetEntity>

Настраивает множество отношений из этого типа сущности.

5

HasOptional<TTargetEntity>

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

6

HasRequired<TTargetEntity>

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

7

Ignore<TProperty>

Исключает свойство из модели, чтобы оно не было сопоставлено с базой данных. (Унаследовано от StructuralTypeConfiguration <TStructuralType>)

8

Property<T>

Настраивает свойство структуры, которое определено для этого типа. (Унаследовано от StructuralTypeConfiguration <TStructuralType>)

9

ToTable(String)

Настраивает имя таблицы, которой сопоставлен этот тип сущности.

Fluent API позволяет настраивать объекты или их свойства, независимо от того, хотите ли вы что-то изменить в том, как они отображаются в базе данных или как они связаны друг с другом. Существует огромное количество отображений и моделирования, на которые вы можете повлиять с помощью конфигураций. Ниже приведены основные типы сопоставлений, которые поддерживает Fluent API.

  • Отображение сущностей
  • Сопоставление свойств

Отображение сущностей

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

  • Поэтому вместо того, чтобы добавлять эти конфигурации в классы домена, мы можем сделать это внутри контекста.

  • Первое, что нужно сделать, это переопределить метод OnModelCreating, который дает modelBuilder для работы.

Схема по умолчанию

При создании базы данных по умолчанию используется схема dbo. Вы можете использовать метод HasDefaultSchema в DbModelBuilder, чтобы указать схему базы данных, которая будет использоваться для всех таблиц, хранимых процедур и т. Д.

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

public class MyContext : DbContext {
   public MyContext() : base("name = MyContextDB") {}

   protected override void OnModelCreating(DbModelBuilder modelBuilder) {
      //Configure default schema
      modelBuilder.HasDefaultSchema("Admin");
   }
	
   public virtual DbSet<Course> Courses { get; set; }
   public virtual DbSet<Enrollment> Enrollments { get; set; }
   public virtual DbSet<Student> Students { get; set; }
}

Сопоставить объект с таблицей

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

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Map entity to table
   modelBuilder.Entity<Student>().ToTable("StudentData");
   modelBuilder.Entity<Course>().ToTable("CourseDetail");
   modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}

Когда база данных будет сгенерирована, вы увидите имя таблицы, указанное в методе OnModelCreating.

Разделение сущностей (отображение сущности в несколько таблиц)

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

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Map entity to table
   modelBuilder.Entity<Student>().Map(sd ⇒ {
      sd.Properties(p ⇒ new { p.ID, p.FirstMidName, p.LastName });
      sd.ToTable("StudentData");
   })

   .Map(si ⇒ {
      si.Properties(p ⇒ new { p.ID, p.EnrollmentDate });
      si.ToTable("StudentEnrollmentInfo");
   });

   modelBuilder.Entity<Course>().ToTable("CourseDetail");
   modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}

В приведенном выше коде вы можете видеть, что сущность Student разделена на следующие две таблицы путем сопоставления некоторых свойств с таблицей StudentData и некоторых свойств с таблицей StudentEnrollmentInfo с помощью метода Map.

  • StudentData - Содержит имя и фамилию учащегося.

  • StudentEnrollmentInfo - Содержит EnrollmentDate.

Когда база данных будет сгенерирована, вы увидите следующие таблицы в своей базе данных, как показано на следующем изображении.

Сопоставление свойств

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

Настройка первичного ключа

Соглашение по умолчанию для первичных ключей -

  • Класс определяет свойство с именем «ID» или «Id».
  • После названия класса идет «ID» или «Id».

Если ваш класс не следует соглашениям по умолчанию для первичного ключа, как показано в следующем коде класса Student:

public class Student {
   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; }
}

Затем, чтобы явно установить свойство как первичный ключ, вы можете использовать метод HasKey, как показано в следующем коде:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
	
   // Configure Primary Key
   modelBuilder.Entity<Student>().HasKey<int>(s ⇒ s.StdntID); 
}

Настроить столбец

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

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Configure EnrollmentDate Column
   modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate)
	
   .HasColumnName("EnDate")
   .HasColumnType("DateTime")
   .HasColumnOrder(2);
}

Настроить свойство MaxLength

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

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
   modelBuilder.Entity<Course>().Property(p ⇒ p.Title).HasMaxLength(24);
}

Настроить свойство Null или NotNull

В следующем примере свойство заголовка курса является обязательным, поэтому метод IsRequired используется для создания столбца NotNull. Аналогично, Student EnrollmentDate не является обязательным, поэтому мы будем использовать метод IsOptional, чтобы разрешить нулевое значение в этом столбце, как показано в следующем коде.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");
   modelBuilder.Entity<Course>().Property(p ⇒ p.Title).IsRequired();
   modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional();
	
   //modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
   //.HasColumnName("FirstName"); 
}

Настройка отношений

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

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

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

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

Настроить индивидуальные отношения

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

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

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

  • Этот тип отношений встречается не часто, потому что большая часть информации, связанной таким образом, будет храниться в одной таблице.

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

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual StudentLogIn StudentLogIn { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

public class StudentLogIn {
   [Key, ForeignKey("Student")]
   public int ID { get; set; }
   public string EmailID { get; set; }
   public string Password { get; set; }
	
   public virtual Student Student { get; set; }
}

Как вы можете видеть в приведенном выше коде, атрибуты Key и ForeignKey используются для свойства ID в классе StudentLogIn, чтобы пометить его как первичный ключ, а также как внешний ключ.

Чтобы настроить отношение один к нулю или один между Student и StudentLogIn с помощью Fluent API, вам необходимо переопределить метод OnModelCreating, как показано в следующем коде.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure ID as PK for StudentLogIn
   modelBuilder.Entity<StudentLogIn>()
   .HasKey(s ⇒ s.ID);

   // Configure ID as FK for StudentLogIn
   modelBuilder.Entity<Student>()
   
   .HasOptional(s ⇒ s.StudentLogIn) //StudentLogIn is optional
   .WithRequired(t ⇒ t.Student); // Create inverse relationship
}

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

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure ID as PK for StudentLogIn
   modelBuilder.Entity<StudentLogIn>()
   .HasKey(s ⇒ s.ID);

   // Configure ID as FK for StudentLogIn
   modelBuilder.Entity<Student>()
   .HasRequired(r ⇒ r.Student)
   .WithOptional(s ⇒ s.StudentLogIn);  
}

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

Настройка отношения "один ко многим"

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

  • В этом типе отношений строка в таблице A может иметь много совпадающих строк в таблице B, но строка в таблице B может иметь только одну совпадающую строку в таблице A.

  • Внешний ключ определяется в таблице, которая представляет собой многосторонний конец отношения.

  • Например, на приведенной выше диаграмме таблицы «Студент» и «Зачисление» связаны отношениями «один ко многим», каждый студент может иметь несколько зачислений, но каждое зачисление принадлежит только одному студенту.

Ниже показаны Студент и Зачисление, которые имеют отношение «один ко многим», но внешний ключ в таблице Зачисления не соответствует принятым по умолчанию соглашениям Code First.

public class Enrollment {
   public int EnrollmentID { get; set; }
   public int CourseID { get; set; }
	
   //StdntID is not following code first conventions name
   public int StdntID { get; set; }
   public Grade? Grade { get; set; }
	
   public virtual Course Course { get; set; }
   public virtual Student Student { get; set; }
}

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual StudentLogIn StudentLogIn { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

В этом случае для настройки отношения «один ко многим» с помощью Fluent API необходимо использовать метод HasForeignKey, как показано в следующем коде.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   //Configure FK for one-to-many relationship
   modelBuilder.Entity<Enrollment>()

   .HasRequired<Student>(s ⇒ s.Student)
   .WithMany(t ⇒ t.Enrollments)
   .HasForeignKey(u ⇒ u.StdntID);  
}

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

В приведенном выше примере метод HasRequired указывает, что свойство навигации Student должно иметь значение Null. Таким образом, вы должны назначать Студенту с сущностью Enrollment каждый раз, когда вы добавляете или обновляете Enrollment. Чтобы справиться с этим, нам нужно использовать метод HasOptional вместо метода HasRequired.

Настройка отношения "многие ко многим"

Каждая запись в обеих таблицах может относиться к любому количеству записей (или никаким записям) в другой таблице.

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

  • Например, таблица "Студент" и таблица "Курс" имеют отношение "многие ко многим".

Ниже приведены классы «Студент» и «Курс», в которых «Студент» и «Курс» связаны отношениями «многие-многие», поскольку оба класса имеют свойства навигации «Студенты» и «Курсы», являющиеся коллекциями. Другими словами, одна сущность имеет другую коллекцию сущностей.

public class Student {
   public int ID { get; set; }
   public string LastName { get; set; }
   public string FirstMidName { get; set; }
   public DateTime EnrollmentDate { get; set; }
	
   public virtual ICollection<Course> Courses { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

public class Course {
   public int CourseID { get; set; }
   public string Title { get; set; }
   public int Credits { get; set; }
	
   public virtual ICollection<Student> Students { get; set; }
   public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Чтобы настроить связь «многие ко многим» между учеником и курсом, вы можете использовать Fluent API, как показано в следующем коде.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure many-to-many relationship
   modelBuilder.Entity<Student>()
   .HasMany(s ⇒ s.Courses) 
   .WithMany(s ⇒ s.Students);
}

Соглашения Code First по умолчанию используются для создания таблицы соединения при создании базы данных. В результате создается таблица StudentCourses со столбцами Course_CourseID и Student_ID, как показано на следующем изображении.

Если вы хотите указать имя объединяемой таблицы и имена столбцов в таблице, вам необходимо выполнить дополнительную настройку с помощью метода Map.

protected override void OnModelCreating(DbModelBuilder modelBuilder) {

   //Configure default schema
   modelBuilder.HasDefaultSchema("Admin");

   // Configure many-to-many relationship 
   modelBuilder.Entity<Student>()

   .HasMany(s ⇒ s.Courses)
   .WithMany(s ⇒ s.Students)
   
   .Map(m ⇒ {
      m.ToTable("StudentCoursesTable");
      m.MapLeftKey("StudentID");
      m.MapRightKey("CourseID");
   }); 
}

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

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