Учебники

Entity Framework – Свободный API

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

  • Свободный API – это еще один способ настройки классов вашего домена.

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

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

Свободный API – это еще один способ настройки классов вашего домена.

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

Свободный 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. Ниже приведены некоторые из них.

Старший Название и описание метода
1

ComplexType <TComplexType>

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

2

Entity <TEntityType>

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

3

HasKey <TKey>

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

4

HasMany <TTargetEntity>

Настраивает множество отношений из этого типа объекта.

5

HasOptional <TTargetEntity>

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

6

HasRequired <TTargetEntity>

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

7

Игнорировать <TProperty>

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

8

Свойство <T>

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

9

ToTable (String)

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

ComplexType <TComplexType>

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

Entity <TEntityType>

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

HasKey <TKey>

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

HasMany <TTargetEntity>

Настраивает множество отношений из этого типа объекта.

HasOptional <TTargetEntity>

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

HasRequired <TTargetEntity>

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

Игнорировать <TProperty>

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

Свойство <T>

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

ToTable (String)

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

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

  • Entity Mapping
  • Картирование свойств

Entity Mapping

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

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

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

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

Первое, что нужно переопределить, – метод 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.

Метод OnModel

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

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

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.

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

В следующем примере свойство Course Title является обязательным, поэтому для создания столбца NotNull используется метод IsRequired. Аналогично, 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.

Если вы используете соглашения об именах 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.

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

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

В этом типе отношений строка в таблице 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 метод

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

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

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

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

  • Например, таблица Student и таблица Course имеют отношение «многие ко многим».

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

Например, таблица Student и таблица Course имеют отношение «многие ко многим».

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

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");
   }); 
}

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

Присоединиться к таблице

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