Можно ли скомпилировать запрос в Entity Framework один раз при старте приложения и не пересобирать его
Что проверяют
Понимаете ли вы, из чего складывается время запроса в EF, и не путаете ли компиляцию LINQ в SQL с планом выполнения на стороне СУБД. Вопрос на трезвость оценок: сильный кандидат сам скажет, что выигрыш скромный.
Как отвечать
Да, запрос компилируется заранее: EF.CompileQuery и EF.CompileAsyncQuery возвращают делегат, который держат в статическом поле и зовут, передавая контекст параметром.
private static readonly Func<AppDbContext, string, IEnumerable<Product>> ByName =
EF.CompileQuery((AppDbContext db, string name) =>
db.Products.Where(p => p.Name == name));Сразу уточните главное: EF и без этого кэширует разобранные запросы. Первый вызов строит дерево выражений и SQL, дальше готовый результат берётся из кэша по ключу. Ручная компиляция экономит вычисление ключа и обход дерева, то есть микросекунды на вызов. Это заметно на горячем пути с тысячами мелких запросов и совершенно незаметно там, где время уходит в сеть и материализацию объектов.
Что важно помнить: контекст передаётся аргументом и никогда не захватывается замыканием, иначе делегат переживёт контекст с временем жизни Scoped. Форма запроса фиксируется при компиляции, поэтому условный Where в него не встроить, на каждый вариант нужен свой делегат.
Где спотыкаются
Первое: смешивают поколения EF. EF.CompileQuery живёт в EF Core, а в EF6 это CompiledQuery.Compile, и подключать при этом пространство имён от EF6 смысла нет. Такая мешанина в ответе видна сразу.
Второе: обещают ускорение в разы. Плана выполнения в СУБД компиляция не касается вовсе, индексы и объём выборки она не меняет. Без замера до и после заявление выглядит пересказом статьи, а не опытом.
Третье: сами портят кэш запросов. Значение, вшитое в дерево константой вместо переменной, даёт новый ключ, и SQL собирается заново. Поведение Contains по списку переменной длины зависит от версии EF, это стоит не утверждать, а проверять замером.