Тюним запросы в EF Core с помощью интерсепторов

от автора

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

В этом случае приходится либо генерировать запросы вручную (напрямую, либо с помощью хранимых процедур), либо прибегать к дополнительной обработке сгенерированных запросов с помощью механизма интерсепторов. Рассмотрим работу этого механизма подробнее.

Немного предыстории

Мне пришлось разбираться с интерсепторами, когда возникла необходимость применить запрос вида SELECT … FOR UPDATE в Postgres. Хотя похожий функционал есть и в SQL Server, EF не поддерживает его «из коробки».

Концепт FOR UPDATE прост, несмотря на сложные механизмы «под капотом». Мы просто говорим, что хотим выбрать какие-либо записи для последующего изменения. Начиная с этого момента, и пока мы не отпустим тем или иным способом транзакцию, их нельзя менять, или выбирать в другом SELECT … FOR UPDATE за пределами текущей транзакции. При этом обычные SELECT работают вполне успешно, равно как и изменение других записей.

На уровне кода всё это достигается добавлением FOR UPDATE в конец запроса, но, как я уже сказал, в Entity Framework нет такого функционала. Зато есть функционал интерсепторов, с помощью которого можно решить поставленную задачу.

Интерсепторы

Принцип работы интерсепторов в EF следующий. Интерсепторы перехватывают и позволяют модифицировать сгенерированный SQL запрос перед его отправкой в базу.

Принцип работы интерсепторов

Принцип работы интерсепторов

По сути, нужно сделать три действия:

  1. Объявить класс-интерсептор, который будет модифицировать наш SQL-запрос нужным образом

  2. Зарегистрировать интерсептор в системе для указанного контекста работы с базой

  3. Как-то передать в него информацию о необходимости активации (если мы не хотим все запросы делать с этой модификацией)

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

public static class CommandExtensions {     public static IQueryable<T> ForUpdate<T>(this IQueryable<T> set) where T : class     {         return set.TagWith("FOR UPDATE");     } }

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

using (ApplicationContext db = new ApplicationContext()) { var trans = db.Database.BeginTransaction(); try     {         var lists = db.Metadata             .Where(p => p.ListId == id)             .ForUpdate()             .ToList();  // код модификации списка trans.Commit();     }     catch     {     trans.Rollback()     throw;     } }

Код интресептора будет выглядеть так. Нам лишь надо убедиться в наличии тега-комментария и применить необходимую трансформацию.

public class ForUpdateInterceptor : DbCommandInterceptor {     public override InterceptionResult<DbDataReader> ReaderExecuting(       DbCommand command,       CommandEventData eventData,       InterceptionResult<DbDataReader> result)     {         if (command.CommandText.Contains("-- FOR UPDATE", StringComparison.InvariantCulture))         {             command.CommandText += " FOR UPDATE";         }         return result;     } }

Класс можно расширить, написав трансформации и для других типов команд ADO.NET. А теперь остаётся только зарегистрировать перехватчик в нашем классе контекста.

public class ApplicationContext : DbContext {     public DbSet<ListMeta> Metadata { get; set; }     public ApplicationContext()     {         Database.EnsureCreated();     }     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)     {     optionsBuilder.UseNpgsql(/* connection sting */)             .AddInterceptors(new ForUpdateInterceptor());     } }

Вывод

Хотя интерсепторы являются в EF узкоспециализированным инструментом, в некоторых случаях они позволяют существенно экономить время и продолжать использовать EF. Особенно в ситуации, где без них пришлось бы откатываться на написание чистого SQL-кода в самом EF или в хранимых процедурах на стороне базы.


ссылка на оригинал статьи https://habr.com/ru/articles/852022/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *