Что произойдет при хранении IQueryable запроса в static переменной в C#
Что проверяют
Понимаете ли вы, что IQueryable это не данные, а дерево выражения вместе с провайдером, и держите ли в голове время жизни контекста базы. Вопрос сразу вскрывает, писал ли кандидат что-то сложнее учебного примера.
Как отвечать
IQueryable хранит выражение и ссылку на провайдер, а провайдер держит DbContext. Положив запрос в статическое поле, вы фактически сделали глобальной ссылку на контекст: он не освободится никогда, а вместе с ним в памяти останется весь отслеживатель изменений с загруженными сущностями. Дальше три сценария:
- контекст уже освобождён, потому что область запроса закончилась: любое перечисление даёт
ObjectDisposedException; - контекст жив и его одновременно используют несколько запросов:
DbContextне потокобезопасен, отсюдаInvalidOperationExceptionпро вторую операцию на том же экземпляре и испорченный отслеживатель; - запрос выполняется заново при каждом перечислении, что само по себе нормально, но идёт он через давнее соединение и чужую транзакцию, а не через текущие.
Хранить статически можно только то, что не тянет контекст: Expression<Func<Order, bool>> с условием фильтра или функцию, которая строит запрос по переданному контексту. Сам IQueryable собирается на свежем контексте в каждом обращении. Если целью была экономия, то план запроса EF Core кеширует сам, а для явного контроля есть скомпилированные запросы. Кешировать имеет смысл результат, то есть обычные данные.
Где спотыкаются
Думают, что статическое поле замораживает данные. Оно ничего не кеширует: при каждом перечислении будет новый поход в базу. Получается худший размен, кеша нет, а проблемы времени жизни есть.
Вызывают ToList внутри нового блока using и считают, что подставили свежий контекст. Запрос выполнится на том контексте, к которому привязан, а новый не используется вообще.
Забывают про внедрение зависимостей. В веб-приложении контекст регистрируется на время запроса, и любая статическая ссылка на него ломает модель. Сюда же попадает IQueryable, возвращённый из метода наружу и перечисленный после закрытия области: AsNoTracking тут не спасает, освобождённый контекст запрос всё равно не выполнит.