Проекция запросов на представления

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

Обычно таблица в базе данных сопоставляется с определенным классом модели в Entity Framework. Но EF Core также позволяет сопоставлять наборы DbSet в контексте данных с представлениями в базе данных, а классы C# - с данными, которые возвращаются этими представлениями.

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

  • Для них не надо определять первичный ключ

  • Изменения в подобных типах не отлеживаются контекстом данных.

  • Мы можем получать данные подобных типов. Но мы не можем добавлять, изменять или удалять объекты подобных типов.

  • Классы сущностей не могут содержать навигационные свойства на подобные типы.

Рассмотрим простой пример. Допустим у нас есть следующие классы сущностей товара и производящей его компании:

public class Product
{
	public int Id { get; set; }
	public string? Name { get; set; }
	public int Price { get; set; }			// цена
	public int TotalCount { get; set; }	 // количество единиц данного товара

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

	public List<Product> Products { get; set; } = new();
}

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

public class CompanyProductsGroup
{
	public string? CompanyName { get; set; }
	public int ProductCount { get; set; }	// количество товаров
	public int TotalSum { get; set; }		// совокупная цена всех товаров компании
}

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

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

using Microsoft.EntityFrameworkCore;

public class ApplicationContext : DbContext
{
    public DbSet<Product> Products { get; set; } = null!;
    public DbSet<Company> Companies { get; set; } = null!;
    public DbSet<CompanyProductsGroup> ProductsByCompany { get; set; } = null!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<CompanyProductsGroup>(pc =>
        {
            pc.HasNoKey();
            pc.ToView("View_ProductsByCompany");
        });
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=helloapp.db");
    }
}

В классе контекста данных мы видим, что определено свойство public DbSet<CompanyProductsGroup> ProductsByCompany { get; set; }. Однако в отличие от других наборов DbSet оно будет сопоставляться с представлением. Для этого для этого типа в методе OnModelCreating() определяются две важные настройки.

Во-первых, указывается, что данная сущность (CompanyProductsGroup) не будет содержать ключа:

pc.HasNoKey();

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

pc.ToView("View_ProductsByCompany");

В данном случае представление называется "View_ProductsByCompany". Оно уже может существовать в базе данных, а может отсутствовать. Далее мы создадим это представление.

Теперь используем все это в программе:

using Microsoft.EntityFrameworkCore;

using (ApplicationContext db = new ApplicationContext())
{
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();
    // создаем представление
    db.Database.ExecuteSqlRaw(@"CREATE VIEW View_ProductsByCompany AS 
                                            SELECT c.Name AS CompanyName, Count(p.Id) AS ProductCount, Sum(p.Price * p.TotalCount) AS TotalSum
                                            FROM Companies c
                                            INNER JOIN Products p on p.CompanyId = c.Id
                                            GROUP BY c.Name");
    // добавляем начальные данные
    Company c1 = new Company { Name = "Apple" };
    Company c2 = new Company { Name = "Samsung" };
    Company c3 = new Company { Name = "Huawei" };
    db.Companies.AddRange(c1, c2, c3);
    Product p1 = new Product { Name = "iPhone X", Company = c1, Price = 70000, TotalCount = 2 };
    Product p2 = new Product { Name = "iPhone 8", Company = c1, Price = 40000, TotalCount = 4 };
    Product p3 = new Product { Name = "Galaxy S9", Company = c2, Price = 42000, TotalCount = 3 };
    Product p4 = new Product { Name = "Galaxy A7", Company = c2, Price = 14000, TotalCount = 5 };
    Product p5 = new Product { Name = "Honor 9", Company = c3, Price = 17000, TotalCount = 7 };
    db.Products.AddRange(p1, p2, p3, p4, p5);
    db.SaveChanges();
}

using (ApplicationContext db = new ApplicationContext())
{
    // обращаемся к представлению
    var companyProducts = db.ProductsByCompany.ToList();
    foreach (var item in companyProducts)
    {
        Console.WriteLine($"Company: {item.CompanyName} Models: {item.ProductCount} Sum: {item.TotalSum}");
    }
}

Для ясности я разделил блок инициализации БД и блок выборки из БД. В первом блоке using, где происходить инициализация базы начальными данными, также добавляется представление с помощью команды CREATE VIEW. (Предсталение можно естественно создать и вручную непосредственно в самой базе данных). Это представление как раз и отвечает за группировку данных. Обратите внимание, что название представления - "View_ProductsByCompany" совпадает с названием представления, с которым сопоставляется класс CompanyProductsGroup, а структура класса совпадает со структорой данных, которые возвращает представление.

db.Database.ExecuteSqlRaw(@"CREATE VIEW View_ProductsByCompany AS 
							SELECT c.Name AS CompanyName, Count(p.Id) AS ProductCount, Sum(p.Price * p.TotalCount) AS TotalSum
							FROM Companies c
							INNER JOIN Products p on p.CompanyId = c.Id
							GROUP BY c.Name");

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

var companyProducts = db.ProductsByCompany.ToList();

Консольный вывод программы:

Company: Apple Models: 2 Sum: 300000
Company: Huawei Models: 1 Sum: 119000
Company: Samsung Models: 2 Sum: 196000

И после выполнения кода в базе данных мы сможем увидеть две таблицы и одно представление:

Предсталения View в Entity Framework Core
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850