Фильтры запросов уровня модели

  • Михаил
  • 12 мин. на прочтение
  • 118
  • 15 Nov 2022
  • 15 Nov 2022

Фильтры запросов уровня модели (Model-level query filters) позволяют определить предикат запроса LINQ непосредственно в метаданных модели (обычно в методе OnModelCreating контекста данных). Такие фильтры автоматически применяются к любым запросам LINQ, в которых используются классы, для которых определен фильтр.

Пусть у нас определены следующие классы:

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

Здесь класс пользователя имеет ссылку на класс роли, к которой он принадлежит. И также определим контекст данных:

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Role> Roles { get; set; }
    public int RoleId { get; set; }
     
    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>().HasQueryFilter(u => u.Age > 17 && u.RoleId == this.RoleId);
    }
}

В метод HasQueryFilter() передается предикат, которому должен удовлетворять объект User, чтобы быть извлеченным из базы данных. То есть в результате запросов будут извлекаться только те объекты User, у которых значение свойства Age больше 17, а свойство RoleId равно значению свойства RoleId их контекста данных.

Используем данные классы:

using (ApplicationContext db = new ApplicationContext())
{
    // пересоздадим базу данных
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();
     
    Role adminRole = new Role { Name = "admin" };
    Role userRole = new Role { Name = "user" };
    User user1 = new User { Name = "Tom", Age = 17, Role = userRole };
    User user2 = new User { Name = "Bob", Age = 18, Role = userRole };
    User user3 = new User { Name = "Alice", Age = 19, Role = adminRole };
    User user4 = new User { Name = "Sam", Age = 20, Role = adminRole };
     
    db.Roles.AddRange(userRole, adminRole);
    db.Users.AddRange(user1, user2, user3, user4);
    db.SaveChanges();
}
using (ApplicationContext db = new ApplicationContext() { RoleId = 2 })
{
    var users = db.Users.Include(u => u.Role).ToList();
    foreach (User user in users)
        Console.WriteLine($"Name: {user.Name}  Age: {user.Age}  Role: {user.Role?.Name}");
}

Результатом будет следующий консольный вывод:

Name: Alice Age:19 Role:admin Name: Sam Age:20 Role:admin

То есть только два объекта из добавленных четырех соответствуют тому предикату, который был передан в HasQueryFilter. И данный фильтр будет действовать для всех запросов к базе данных, которые извлекают данные из таблицы Users. Например, нахождение минимального возраста:

using (ApplicationContext db = new ApplicationContext() { RoleId = 2 })
{
    int minAge = db.Users.Min(x => x.Age);
    Console.WriteLine(minAge);  // 19
}

Несмотря на то, что минимальный возраст по всем 4 объектам составляет 17, но так как действует фильтр, при запросе будут учитываться только те объекты в бд, которые соответствуют фильтру.

Если необходимо во время запроса отключить фильтр, то применяется метод IgnoreQueryFilters():

using (ApplicationContext db = new ApplicationContext() { RoleId = 2 })
{
    int minAge = db.Users.IgnoreQueryFilters().Min(x => x.Age);
    Console.WriteLine(minAge);  // 17
}