What is entity framework?

Хранимые процедуры

Последнее обновление: 25.10.2019

Не меньше хранимых функций для взаимодействия с базой данных применяются хранимые процедуры. Рассмотрим, как вызывать хранимые процедуры из кода на C# через Entity Framework Core.

Пусть наша база данных, как и в прошлой теме, описывается следующим контекстом данных и моделями:


public class ApplicationContext : DbContext
{
    public DbSet<Company> Companies { get; set; }
    public DbSet<Phone> Phones { get; set; }
	public ApplicationContext()
	{
		Database.EnsureCreated();
	}
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=mobileappdb;Trusted_Connection=True;");
    }
}

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Phone> Phones { get; set; }
    public Company()
    {
        Phones = new List<Phone>();
    }
}

public class Phone
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }

    public int CompanyId { get; set; }
    public Company Company { get; set; }
}

Теперь в Visual Studio в окне SQL Server Object Explorer откроем узел базы данных. В узле базы данных откроем подузел Programmability -> Stored Procedures:

Далее нажмем на этот узел правой кнопкой мыши и выберем Add New Stored Procedure:

После этого Visual Studio генерирует и октрывает файл скрипта хранимой процедуры со следующим содержимым:

CREATE PROCEDURE .
	@param1 int = 0,
	@param2 int
AS
	SELECT @param1, @param2
RETURN 0

Изменим скрипт следующим образом:

CREATE PROCEDURE .
    @name nvarchar(50) 
AS
    SELECT * FROM Phones 
    WHERE CompanyId=(SELECT Id FROM Companies WHERE Name=@name)
GO

Данная процедура ищет все строки, где значение столбца название компании равно строке, переданной через параметр @name.

Далее нажмем на кнопку Update для добавления хранимой процедуры:

И затем в появившемся окошке нажимаем кнопку Update Database:

После этого в узле Stored Procedures появится новая хранимая процедура, и мы сможем ее использовать.

Теперь обратимся к процедуре в коде C#:

using(ApplicationContext db = new ApplicationContext())
{
    Microsoft.Data.SqlClient.SqlParameter param = new Microsoft.Data.SqlClient.SqlParameter("@name", "Samsung");
	var phones = db.Phones.FromSqlRaw("GetPhonesByCompany @name", param).ToList();
    foreach (var p in phones)
        Console.WriteLine($"{p.Name} - {p.Price}");
}

Параметр в методе принимает название процедуры, после которого идет перечисление параметров:

Выходные параметры процедуры

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

CREATE PROCEDURE .
	@name varchar(50) OUTPUT
AS
	SELECT @name =  FROM Phones WHERE Price = (SELECT MAX(Price) FROM Phones)
RETURN 0

Параметр здесь определен как выходной с ключевым словом OUTPUT. Через него будет передаваться название смартфона.

Обратимся к этой процедуре в коде C#:

using(ApplicationContext db = new ApplicationContext())
{
    var param = new Microsoft.Data.SqlClient.SqlParameter
	{
		ParameterName = "@phoneName",
		SqlDbType = System.Data.SqlDbType.VarChar,
		Direction = System.Data.ParameterDirection.Output,
		Size = 50
	};
	db.Database.ExecuteSqlRaw("GetPhoneWithMaxPrice @phoneName OUT", param);
    Console.WriteLine(param.Value);
}

Здесь параметр phoneName определен как выходной. Так как нам в данном случае не надо возвращать набор данных, который соответствует одной из моделей, то для выполнения запроса используется метод . И после его выполнения через свойство param.Value мы сможем получить значение, переданное через параметр.

НазадВперед

IEnumerable и IQueryable

Последнее обновление: 26.10.2019

При вызове методов LINQ мы только создаем запрос. Его непосредственное выполнение происходит, когда мы начинаем потреблять результаты этого запроса. Нередко это происходит при переборе результата запроса в цикле for или при применении к нему ряда методов — ToList или ToArray, а также если запрос представляет скалярное значение, например, метод Count.

В процессе выполнения запросов LINQ to Entities мы может получать два объекта, которые предоставляют наборы данных: IEnumerable и IQueryable. С одной стороны, интерфейс IQueryable наследуется от IEnumerable, поэтому по идее объект IQueryable это и есть также объект IEnumerable. Но реальность несколько сложнее. Между объектами этих интерфейсов есть разница в плане функциональности, поэтому они не взаимозаменяемы.

Интерфейс IEnumerable находится в пространстве имен System.Collections и System.Collections.Generic (обобщенная версия). Объект IEnumerable представляет набор данных в памяти и может перемещаться по этим данным только вперед. Запрос, представленный объектом IEnumerable, выполняется немедленно и полностью, поэтому получение данных приложением происходит быстро.

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

Интерфейс IQueryable располагается в пространстве имен System.Linq. Объект IQueryable предоставляет удаленный доступ к базе данных и позволяет перемещаться по данным как в прямом порядке от начала до конца, так и в обратном порядке. В процессе создания запроса, возвращаемым объектом которого является IQueryable, происходит оптимизация запроса. В итоге в процессе его выполнения тратится меньше памяти, меньше пропускной способности сети, но в то же время он может обрабатываться чуть медленнее, чем запрос, возвращающий объект IEnumerable.

Для примера используем следующую модель и контекст данных:

public class User
{
	public int Id { get; set; }
	public string Name { get; set; }
}
public class ApplicationContext : DbContext
{
	public DbSet<User> Users { get; set; }
	public ApplicationContext()
	{
		Database.EnsureDeleted();
		Database.EnsureCreated();
	}
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=helloappdb;Trusted_Connection=True;");
	}
	protected override void OnModelCreating(ModelBuilder modelBuilder)
	{
		// добавляем один объект
		modelBuilder.Entity<User>().HasData(new User { Id=1, Name="Tom"});
	}
}

Возьмем два вроде бы идентичных выражения. Объект IEnumerable:

int id = 1;
IEnumerable<User> userIEnum = db.Users;
var users=userIEnum.Where(p => p.Id > id).ToList();

Здесь запрос будет иметь следующий вид:

SELECT ., .
FROM  AS 

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

Чтобы совместить фильтры, нам надо было сразу применить метод Where:

Объект IQueryable:

int id = 1;
IQueryable<User> userIQuer = db.Users;
var users=userIQuer.Where(p => p.Id > id).ToList();

Здесь запрос будет иметь следующий вид:

SELECT ., .
FROM  AS 
WHERE . > 1

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

Это позволяет динамически создавать сложные запросы. Например, мы можем последовательно наслаивать в зависимости от условий выражения для фильтрации:

IQueryable<User> userIQuer = db.Users;
userIQuer = userIQuer.Where(p => p.Id < 7);
userIQuer = userIQuer.Where(p => p.Name == "Tom"); 
var users = userIQuer.ToList();

В данном случае будет создаваться следующий SQL-запрос:

SELECT ., .
FROM  AS 
WHERE (. < 7) AND ((. = N'Tom') AND . IS NOT NULL)

Что же лучше использовать? Все зависит от конкретной ситуации. Если разработчику нужен весь набор возвращаемых данных, то лучше использовать IEnumerable, предоставляющий максимальную скорость. Если же нам не нужен весь набор, а то только некоторые отфильтрованные данные, то лучше применять IQueryable.

НазадВперед

Как использовать Entity Framework

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

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


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

Несмотря на то что класс называется PlayerContext, его можно использовать для работы с любыми другими сущностями. Для этого нужно только добавить ещё несколько коллекций DbSet.

Название таблицы должно представлять собой множественное число от названия сущности: Player — Players, Person — People.

Теперь всё готово и можно приступить к работе с базой данных. Начать стоит с объявления первых объектов и их добавления в БД.

Запустив программу с таким кодом, можно увидеть, что всё успешно сохранилось:

Теперь пора проверить, есть ли что-нибудь в базе данных в таблице Players. Для этого можно просто вывести данные из объектов в свойстве Players.

Вот что должно получиться:

Тут видно, что Entity Framework автоматически указал свойство Id как первичный ключ, поэтому значения заполняются автоматически.

Delete Operation

To delete an entity using Entity Framework, you use the Remove method on DbSet. Remove works for both existing and newly added entities. Calling Remove on an entity that has been added but not yet saved to the database will cancel the addition of the entity. The entity is removed from the change tracker and is no longer tracked by the DbContext. Calling Remove on an existing entity that is being change-tracked will register the entity for deletion the next time SaveChanges is called. The following example is of a code where the student is removed from the database whose first name is Ali.

using (var context = new UniContextEntities()) {
   var bay = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
   context.Students.Remove(bay);
   context.SaveChanges();
}

EF Core vs EF 6

Entity Framework Core is the new and improved version of Entity Framework for .NET Core applications. EF Core is new, so still not as mature as EF 6.

EF Core continues to support the following features and concepts, same as EF 6.

  1. DbContext & DbSet
  2. Data Model
  3. Querying using Linq-to-Entities
  4. Change Tracking
  5. SaveChanges
  6. Migrations

EF Core will include most of the features of EF 6 gradually. However, there are some features of EF 6 which are not supported in EF Core 2.0 such as:

  1. EDMX/ Graphical Visualization of Model
  2. Entity Data Model Wizard (for DB-First approach)
  3. ObjectContext API
  4. Querying using Entity SQL.
  5. Automated Migration
  6. Inheritance: Table per type (TPT)
  7. Inheritance: Table per concrete class (TPC)
  8. Many-to-Many without join entity
  9. Entity Splitting
  10. Spatial Data
  11. Lazy loading of related data
  12. Stored procedure mapping with DbContext for CUD operation
  13. Seed data
  14. Automatic migration

EF Core includes the following new features which are not supported in EF 6.x:

  1. Easy relationship configuration
  2. Batch INSERT, UPDATE, and DELETE operations
  3. In-memory provider for testing
  4. Support for IoC (Inversion of Control)
  5. Unique constraints
  6. Shadow properties
  7. Alternate keys
  8. Global query filter
  9. Field mapping
  10. DbContext pooling
  11. Better patterns for handling disconnected entity graphs

Learn more on EF Core and EF 6 differences at here.

Create Operation

Adding a new object with Entity Framework is as simple as constructing a new instance of your object and registering it using the Add method on DbSet. The following code lets you add a new student to the database.

class Program {

   static void Main(string[] args) {

      var newStudent = new Student();

      //set student name

      newStudent.FirstMidName = "Bill";
      newStudent.LastName = "Gates";
      newStudent.EnrollmentDate = DateTime.Parse("2015-10-21");
      newStudent.ID = 100;

      //create DBContext object

      using (var dbCtx = new UniContextEntities()) {

         //Add Student object into Students DBset
         dbCtx.Students.Add(newStudent);

         // call SaveChanges method to save student into database
         dbCtx.SaveChanges();
      }
   }
}

PostgreSQL

Последнее обновление: 27.10.2019

Для работы с базой данных PostgreSQL создадим новый консольный проект .NET Core. Для работы с этой СУБД вместо с Entity Framework Core в проект необходимо добавить через Nuget пакет Npgsql.EntityFrameworkCore.PostgreSQL:

После установки пакета определим в проекте класс User:

public class User
{
	public int Id { get; set; }
	public string Name { get; set; }
	public int Age { get; set; }
}

И также определим класс контекста данных:

using Microsoft.EntityFrameworkCore;

namespace PostgresApp
{
    public class ApplicationContext : DbContext
    {
        public DbSet<User> Users { get; set; }
		
		public ApplicationContext()
        {
            Database.EnsureCreated();
        }
		
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=usersdb;Username=postgres;Password=password");
        }
    }
}

Для установки подключения к базе данных в методе OnConfiguring вызывается метод UseNpgsql(), в который передается строка подключения. Строка подключения содержит адрес сервера (параметр Host), порт (Port), название базы данных на сервере (Database), имя пользователя в рамках сервера PostgreSQL (Username) и его пароль (Password). В зависимости от настроек сервера PostgreSQL параметры могут отличаться.

Теперь определим в файле Program.cs простейшую программу по добавлению и извлечению объектов из базы данных:

using System;
using System.Linq;

namespace PostgresApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ApplicationContext db = new ApplicationContext())
            {
                // создаем два объекта User
                User user1 = new User { Name = "Tom", Age = 33 };
                User user2 = new User { Name = "Alice", Age = 26 };

                // добавляем их в бд
                db.Users.Add(user1);
                db.Users.Add(user2);
                db.SaveChanges();

                // получаем объекты из бд и выводим на консоль
                var users = db.Users.ToList();
                Console.WriteLine("Users list:");
                foreach (User u in users)
                {
                    Console.WriteLine($"{u.Id}.{u.Name} - {u.Age}");
                }
            }
            Console.Read();
        }
    }
}

Консольный вывод:

Users list:
1.Tom - 33
2.Alice - 26

Миграции

Выше для создания базы данных использовался метод Database.EnsureCreated. Теперь изменим класс контекста данных — уберем вызов Database.EnsureCreated и изменим название база данных:

public class ApplicationContext : DbContext
{
	public DbSet<User> Users { get; set; }

	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=usersdb2;Username=postgres;Password=password");
	}
}

Посмотрим теперь, как использовать миграции. Прежде всего нам надо добавить в проект через Nuget пакет Microsoft.EntityFrameworkCore.Tools для поддержки миграций.

Для создания базы данных создадим и выполним миграции. Для этого в окне Package Manager Console введем команду:

Add-Migration Initial

После генерации файла миграции для создания базы данных выполним команду:

Update-Database

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

Подключение к существующей базе данных

Для подключения к существующей базе данных в PostgreSQL необходимо в Package Manager Console выполнить команду Scaffold-DbContext, которой передается строка подключения и название провайдера (то есть Npgsql.EntityFrameworkCore.PostgreSQL):

Scaffold-DbContext "Host=localhost;Port=5432;Database=usersdb;Username=postgres;Password=password" 
Npgsql.EntityFrameworkCore.PostgreSQL

НазадВперед

Работа с данными

Теперь у нас есть база данных, сгенерированная из модели EDMX и мы можем продемонстрировать работу Entity Framework на примере нашего приложения ASP.NET. Для этого выполните следующие шаги:

На данном этапе в таблице CustomerSet базы данных TestCustomer отсутствуют какие-либо данные. Давайте исправим это. Откройте в Visual Studio окно обозревателя серверов Server Explorer с помощью команды View —> Server Explorer. Вы увидите подключение к базе данных Model1Container, как показано на рисунке ниже (это подключение было автоматически добавлено в окно Server Explorer при генерации базы данных): Щелкните правой кнопкой мыши по имени таблицы CustomerSet и выберите в контекстном меню пункт New Query. Выполните следующий SQL-код, для вставки данных в таблицу:

Вставьте в проект новую веб-форму по имени Default.aspx. Для этого щелкните правой кнопкой мыши по имени проекта в окне Solution Explorer и выберите пункт Add New Item. В открывшемся диалоговом окне выберите шаблон Web Form. Измените разметку формы:

В этом примере мы используем элемент управления ListView для отображения и редактирования данных. Теперь откройте файл отделенного кода веб-формы Default.aspx.cs и введите следующий код:

Класс Model1Container был автоматически создан при создании модели EDMX. Он унаследован от класса DbContext – центрального класса Entity Framework, представляющий абстрактный контейнер для хранения спроецированных данных из БД. Теперь мы можем использовать возможности Entity Framework, для того, чтобы реализовать методы чтения, удаления, изменения и вставки данных – их еще называют методами CRUD (create read update delete)

Вставьте следующий код в файл Default.aspx.cs:

Обратите внимание, что свойство CustomerSet класса Model1Container представляет собой таблицу CustomerSet в базе данных (приставка Set была автоматически добавлена при генерации базы данных). Фактически это свойство возвращает коллекцию всех покупателей

Приведение значения этого свойства к типу IQueryable в методе GetCustomers() является одной из оптимизаций запросов Entity Framework, известной как ленивая загрузка (lazy loading), позволяющая выполнять отложенный LINQ-запрос (отложенное выполнение запросов является главным отличием типов IEnumerable и IQueryable). Запустите проект и проверьте функциональность созданной веб-формы:

Итак, с помощью нескольких строк кода, мы получили полнофункциональное приложение, позволяющее выполнять простые операции для работы с базой данных. Фактически, при разработке данного примера, я потратил куда больше времени на создание разметки элемента управления ListView, нежели чем на код методов-обработчиков событий. Это и является одной из самых привлекательных сторон Entity Framework, если бы я писал код на классическом ADO.NET, у меня бы ушла уйма времени на написание кода доступа к данным, составление SQL-команд и т.д.

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

Кроме того, изменения, сделанные разработчиком в модели EDMX автоматически отображаются на базу данных и наоборот, изменения сделанные в базе данных, например администратором, отображаются на модели EDMX (этот момент мы не рассмотрели выше, в дизайнере EDM вы можете щелкнуть правой кнопкой мыши по нужному объекту и выбрать в контекстном меню пункт Update Model from Database для обновления модели из базы данных). Эти возможности появляются за счет автоматизации процесса отображения данных на объектную модель приложения (с помощью средств Visual Studio).

Отложенная загрузка (lazy loading)

Отложенная загрузка (lazy loading) заключается в том, что Entity Framework автоматически загружает данные, при этом не загружая связанные данные. Когда потребуются связанные данные Entity Framework создаст еще один запрос к базе данных. В контексте нашего примера это означает, что вы можете, например, загрузить первого заказчика из таблицы Customers и сохранить его в переменной customer. Затем вам может понадобиться узнать, какие заказы связаны с этим покупателем. Напомню, в классе модели Customer у нас определено навигационное свойство Orders. Если вы обратитесь к этому свойству (customer.Orders), то Entity Framework отправит запрос в базу данных на извлечение всех связанных с этим покупателем заказов.


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

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

Для того, чтобы использовать динамические прокси-объекты, и, следовательно, отложенную загрузку, есть пара условий, которым должен соответствовать ваш класс модели. Если эти условия не выполняются, Entity Framework, не будет создавать динамические прокси-объекты для класса и будет просто возвращать экземпляры вашего класса POCO, которые не могут выполнять отложенную загрузку, а следовательно, взаимодействовать с базой данных:

  • Ваш класс модели должен иметь модификатор доступа public и не должен запрещать наследование (ключевое слово sealed в C#).

  • Навигационные свойства, которые должны обеспечивать отложенную загрузку, должны быть виртуальными (модификатор virtual).

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

Если вы теперь вызовите этот метод в главном методе Main(), то никакие связанные данные не будут загружаться (т.к. мы еще не изучили как вставлять данные с помощью EF, вам нужно будет вручную вставить какие-нибудь данные в таблицу Orders с помощью Visual Studio или SQL Server Management Studio, для тестирования последующих примеров). В этом примере не происходит отложенная загрузка, т.к. наш класс модели Customer не подходит под второе условие – навигационное свойство Customer.Orders не является виртуальным. Давайте изменим это:

Теперь, при запуске приложения Entity Framework создаст динамически прокси-объект для класса Customer и извлечет данные заказов из базы при их запросе. На рисунке ниже наглядно показано какие данные загружает Entity Framework в этом примере:

Отложенная загрузка является очень простой в использовании, потому что используется автоматически. При этом, она является довольно опасной в плане производительности! Что если мы захотим извлечь сначала всех пользователей используя не отложенный запрос, а затем захотим извлечь все заказы, связанные с этими пользователями:

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

Why Entity Framework?

Entity Framework is an ORM and ORMs are aimed to increase the developer’s productivity by reducing the redundant task of persisting the data used in the applications.

  • Entity Framework can generate the necessary database commands for reading or writing data in the database and execute them for you.

  • If you’re querying, you can express your queries against your domain objects using LINQ to entities.

  • Entity Framework will execute the relevant query in the database and then materialize results into instances of your domain objects for you to work within your app.

There are other ORMs in the marketplace such as NHibernate and LLBLGen Pro. Most ORMs typically map domain types directly to the database schema.

Entity Framework has a more granular mapping layer so you can customize mappings, for example, by mapping the single entity to multiple database tables or even multiple entities to a single table.

  • Entity Framework is Microsoft’s recommended data access technology for new applications.

  • ADO.NET seems to refer directly to the technology for data sets and data tables.

  • Entity Framework is where all of the forward moving investment is being made, which has been the case for a number of years already.

  • Microsoft recommends that you use Entity Framework over ADO.NET or LINQ to SQL for all new development.

Association Type

It is another fundamental building block for describing relationships in EDM. In a conceptual model, an association represents a relationship between two entity types such as Student and Enrollment.

  • Every association has two association ends that specify the entity types involved in the association.

  • Each association end also specifies an association end multiplicity that indicates the number of entities that can be at that end of the association.

  • An association end multiplicity can have a value of one (1), zero or one (0..1), or many (*).

  • Entities at one end of an association can be accessed through navigation properties, or through foreign keys if they are exposed on an entity type.

Скомпилированные запросы

Последнее обновление: 19.08.2017

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

Хотя EF Core может автоматически компилировать и кэшировать запросы на основании хэш-представления выражений LINQ, тем не менее компилируемые запросы позволяют хоть немножно но повысить производительность, так как в этом случае не вычисляется хэш-значение и не производится поиск в кэше, а приложение может использовать уже скомпилированные запросы через вызов делегатов.

Для компиляции запроса набор выражений LINQ передается в метод EF.CompileQuery(), а скомпилированный запрос затем присваивается делегату:

делегат = EF.CompileQuery((параметры) => выражения_LINQ)

Например, возьмем следующие модели:

public class ApplicationContext : DbContext
{
	public DbSet<Company> Companies { get; set; }
	public DbSet<Phone> Phones { get; set; }
	public ApplicationContext()
	{
		Database.EnsureCreated();
	}
	protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
	{
		optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=mobileappdb;Trusted_Connection=True;");
	}
}
public class Company
{
	public int Id { get; set; }
	public string Name { get; set; }

	public List<Phone> Phones { get; set; }
	public Company()
	{
		Phones = new List<Phone>();
	}
}

public class Phone
{
	public int Id { get; set; }
	public string Name { get; set; }
	public int Price { get; set; }
	public int CompanyId { get; set; }
	public Company Company { get; set; }
}

Создадим и используем пару скомпилированных запросов.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;

namespace HelloApp
{
    public class Program
    {
        private static Func<ApplicationContext, int, Phone> phoneById =
            EF.CompileQuery((ApplicationContext db, int id) => 
                    db.Phones.Include(c => c.Company).FirstOrDefault(c => c.Id == id));

        private static Func<ApplicationContext, string, int, IEnumerable<Phone>> phonesByNameAndPrice =
            EF.CompileQuery((ApplicationContext db, string name, int price) =>
                    db.Phones.Include(c => c.Company)
                            .Where(p=>EF.Functions.Like(p.Name, name) && p.Price>price));

        public static void Main(string[] args)
        {
            using (ApplicationContext db = new ApplicationContext())
            {
                var phone = phoneById(db, 1);
                Console.WriteLine($"{phone.Name} - {phone.Price} \n");

                var phones = phonesByNameAndPrice(db, "%Galaxy%", 30000).ToList();
                foreach (var p in phones)
                    Console.WriteLine($"{p.Name} - {p.Price}");
            }

            Console.Read();
        }
    }
}

Здесь определены два делегата, которые фактически представляют два запроса — phoneById (ищет телефон по id) и phonesByNameAndPrice (ищет телефоны по имени и цене).

Делегат phoneById принимает два входных параметра — контекст данных и id объекта и выходной параметр — найденный объект Phone. В метод EF.CompileQuery для компиляции запроса передается лямбда-выражение, параметры которого — это входные параметры делегата, а тело которого — собственно набор выражений LINQ:

private static Func<ApplicationContext, int, Phone> phoneById =
	EF.CompileQuery((ApplicationContext db, int id) => 
		db.Phones.Include(c => c.Company).FirstOrDefault(c => c.Id == id));

Делегат phonesByNameAndPrice принимает три входных параметра — контекст данных, часть названия телефона и его цена. Выходной параметр — набор объектов в виде типа IEnumerable<Phone>. Этот делегат также передает в EF.CompileQuery свои входные параметры и получает некоторый результат:

private static Func<ApplicationContext, string, int, IEnumerable<Phone>> phonesByNameAndPrice =
	EF.CompileQuery((ApplicationContext db, string name, int price) =>
		db.Phones.Include(c => c.Company)
				 .Where(p=>EF.Functions.Like(p.Name, name) && p.Price>price));

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

var phone = phoneById(db, 1);
var phones = phonesByNameAndPrice(db, "%Galaxy%", 30000).ToList();

НазадВперед

Features

Following are the basic features of Entity Framework. This list is created based on the most notable features and also from frequently asked questions about Entity Framework.

  • Entity Framework is a Microsoft tool.
  • Entity Framework is being developed as an Open Source product.
  • Entity Framework is no longer tied or dependent to the .NET release cycle.
  • Works with any relational database with valid Entity Framework provider.
  • SQL command generation from LINQ to Entities.
  • Entity Framework will create parameterized queries.
  • Tracks changes to in-memory objects.
  • Allows to insert, update and delete command generation.
  • Works with a visual model or with your own classes.
  • Entity Framework has stored Procedure Support.

Previous Page Print Page

Next Page  

Schema Definition Language

ADO.NET Entity Framework uses an XML based Data Definition Language called Schema Definition Language (SDL) to define the EDM Schema.

  • The SDL defines the Simple Types similar to other primitive types, including String, Int32, Double, Decimal, and DateTime, among others.

  • An Enumeration, which defines a map of primitive values and names, is also considered a simple type.

  • Enumerations are supported from framework version 5.0 onwards only.

  • Complex Types are created from an aggregation of other types. A collection of properties of these types define an Entity Type.

The data model primarily has three key concepts to describe data structure −

  • Entity type
  • Association type
  • Property

EF Core Development Approaches

EF Core supports two development approaches 1) Code-First 2) Database-First. EF Core mainly targets the code-first approach and provides little support for the database-first approach because the visual designer or wizard for DB model is not supported as of EF Core 2.0.

In the code-first approach, EF Core API creates the database and tables using migration based on the conventions and configuration provided in your domain classes. This approach is useful in Domain Driven Design (DDD).

In the database-first approach, EF Core API creates the domain and context classes based on your existing database using EF Core commands. This has limited support in EF Core as it does not support visual designer or wizard.

Conceptual Model

For developers who are used to database focused development, the biggest shift with Entity Framework is that it lets you focus on your business domain. What it is that you want your application to do without being limited by what the database is able to do?

  • With Entity Framework, the focal point is referred to as a conceptual model. It’s a model of the objects in your application, not a model of the database you use to persist your application data.

  • Your conceptual model may happen to align with your database schema or it may be quite different.

  • You can use a Visual Designer to define your conceptual model, which can then generate the classes you will ultimately use in your application.

  • You can just define your classes and use a feature of Entity Framework called Code First. And then Entity Framework will comprehend the conceptual model.

Either way, Entity Framework works out how to move from your conceptual model to your database. So, you can query against your conceptual model objects and work directly with them.

LINQ-to-Entities

Language-Integrated Query (LINQ) is a powerful query language introduced in Visual Studio 2008. As the name suggests, LINQ-to-Entities queries operate on the entity set ( type properties) to access the data from the underlying database. You can use the LINQ method syntax or query syntax when querying with EDM. Visit LINQ Tutorials to learn LINQ step-by-step.

The following sample LINQ-to-Entities query fetches the data from the table in the database.

LINQ Method syntax:

//Querying with LINQ to Entities 
using (var context = new SchoolDBEntities())
{
    var query = context.Students
                       .where(s => s.StudentName == "Bill")
                       .FirstOrDefault<Student>();
}

LINQ Query syntax:

using (var context = new SchoolDBEntities())
{
    var query = from st in context.Students
                where st.StudentName == "Bill"
                select st;
   
    var student = query.FirstOrDefault<Student>();
}

As you can see above, we created an instance of the context class . It is recommended to instantiate it in , so that once it goes out of scope, then it automatically gets disposed.

Learn different types of LINQ-to-Entities projection query in the next chapter.


С этим читают