Запросы LINQ to Entities

Введение в LINQ to Entities

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

Для извлечения данных из базы данных Entity Framework Core использует технологию LINQ to Entities. В основе данной технологии лежит язык интегрированных запросов LINQ (Language Integrated Query). LINQ предлагает простой и интуитивно понятный подход для получения данных с помощью выражений, которые по форме близки выражениям языка SQL.

Хотя при работе с базой данных мы оперируем запросами LINQ, но, реляционные базы данных как MS SQL Server или SQLite, понимают только запросы на языке SQL. Поэтому Entity Framework Core, используя выражения LINQ to Entities, транслирует их в определенные запросы, понятные для используемого источника данных.

Построение запросов

Создавать запросы мы можем двумя способами: через операторы LINQ и через методы расширения. Пусть для рассмотрения основных запросов в LINQ to Entities у нас будут следующие модели со связью один-ко-многим:

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

    public List<User> Users { get; set; } = new();
}

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

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

И пусть будет следующий контекст данных:

using Microsoft.EntityFrameworkCore;

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; } = null!; 
    public DbSet<Company> Companies { get; set; } = null!;
	
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=helloapp.db");
    }
}

Вначале используем некоторые операторы LINQ:

using Microsoft.EntityFrameworkCore;

using (ApplicationContext db = new ApplicationContext())
{
    // пересоздаем базу данных
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();

    Company microsoft = new Company { Name = "Microsoft" };
    Company google = new Company { Name = "Google" };
    db.Companies.AddRange(microsoft, google);

    User tom = new User { Name = "Tom", Age = 36, Company = microsoft };
    User bob = new User { Name = "Bob", Age = 39, Company = google };
    User alice = new User { Name = "Alice", Age = 28, Company = microsoft };
    User kate = new User { Name = "Kate", Age = 25, Company = google };

    db.Users.AddRange(tom, bob, alice, kate);
    db.SaveChanges();
}
using (ApplicationContext db = new ApplicationContext())
{
    var users = (from user in db.Users.Include(p => p.Company)
                 where user.CompanyId == 1
                 select user).ToList();

    foreach (var user in users)
        Console.WriteLine($"{user.Name} ({user.Age}) - {user.Company?.Name}");
}

Вначале здесь происходит добавление данных, если они отсутствуют. Далее примяются Linq-операторы:

var users = (from user in db.Users.Include(p=>p.Company)
              where user.CompanyId == 1
              select user).ToList();

Данный запрос говорит, что из набора db.Users надо выбрать каждый объект в переменную user. Далее с помощью оператора where проводится фильтрация объектов, и если объект соответствует критерию (в данном случае id компании должно равняться 1), то этот объект передается дальше. И в конце оператор select передает выбранные значения в результирующую выборку, которая возвращается LINQ-выражением.

В итоге мы получим следующий консольный вывод:

Tom (36) - Microsoft
Alice (28) - Microsoft

Теперь для получения данных используем методы расширения LINQ:

using(ApplicationContext db = new ApplicationContext())
{
    var users = db.Users.Include(p=>p.Company).Where(p=> p.CompanyId == 1);
}

Оба запроса в итоге транслируются в одно и то же выражение sql:

SELECT "u"."Id", "u"."Age", "u"."CompanyId", "u"."Name", "c"."Id", "c"."Name"
FROM "Users" AS "u"
INNER JOIN "Companies" AS "c" ON "u"."CompanyId" = "c"."Id"
WHERE "u"."CompanyId" = 1

Основные методы, которые мы можем использовать для создания запросов в Entity Framework Core:

  • All / AllAsync: возвращает true, если все элементы набора удовлятворяют определенному условию, иначе возвращает false

  • Any / AnyAsync: возвращает true, если хотя бы один элемент набора определенному условию

  • Average / AverageAsync: подсчитывает cреднее значение числовых значений в наборе

  • Contains / ContainsAsync: определяет, содержит ли набор определенный элемент

  • Count / CountAsync: подсчитывает количество элементов в наборе

  • First / FirstAsync: выбирает первый элемент коллекции

  • FirstOrDefault / FirstOrDefaultAsync: выбирает первый элемент коллекции или возвращает значение по умолчанию

  • Single / SingleAsync: выбирает единственный элемент коллекции, если коллекция содердит больше или меньше одного элемента, то генерируется исключение

  • SingleOrDefault / SingleOrDefaultAsync: выбирает единственный элемент коллекции или возвращает значение по умолчанию

  • Select: определяет проекцию выбранных значений

  • Where: определяет фильтр выборки

  • OrderBy: упорядочивает элементы по возрастанию

  • OrderByDescending: упорядочивает элементы по убыванию

  • ThenBy: задает дополнительные критерии для упорядочивания элементов возрастанию

  • ThenByDescending: задает дополнительные критерии для упорядочивания элементов по убыванию

  • Join: соединяет два набора по определенному признаку

  • GroupBy: группирует элементы по ключу

  • Except: возвращает разность двух наборов, то есть те элементы, которые содератся только в одном наборе

  • Union: объединяет два однородных набора

  • Intersect: возвращает пересечение двух наборов, то есть те элементы, которые встречаются в обоих наборах элементов

  • Sum / SumAsync: подсчитывает сумму числовых значений в коллекции

  • Min / MinAsync: находит минимальное значение

  • Max / MaxAsync: находит максимальное значение

  • Take: выбирает определенное количество элементов с начала последовательности

  • TakeLast: выбирает определенное количество элементов с конца последовательности

  • Skip: пропускает определенное количество элементов с начала последовательности

  • SkipLast: пропускает определенное количество элементов с конца последовательности

  • TakeWhile: возвращает цепочку элементов последовательности, до тех пор, пока условие истинно

  • SkipWhile: пропускает элементы в последовательности, пока они удовлетворяют заданному условию, и затем возвращает оставшиеся элементы

  • ToList / ToListAsync: получения списка объектов

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

using Microsoft.EntityFrameworkCore;

using (ApplicationContext db = new ApplicationContext())
{
    var users = await db.Users
                        .Include(p => p.Company)
                        .Where(p => p.CompanyId == 1)
                        .ToListAsync();     // асинхронное получение данных

    foreach (var user in users)
        Console.WriteLine($"{user.Name} ({user.Age}) - {user.Company?.Name}");
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850