Почему запрос может не использовать индекс в SQL Server?
Что проверяют
Понимаете ли вы, что между вашим LINQ и планом выполнения стоят два переводчика: провайдер EF Core и оптимизатор сервера. Смотрят, умеете ли вы посмотреть итоговый SQL и связать его с тем, что показывает план.
Как отвечать
Начните с общего: индекс не используется, когда предикат несаргируемый или когда оптимизатор считает сканирование дешевле. Дальше переведите это в типичные для C# причины.
Несовпадение типов. По умолчанию string уезжает параметром типа nvarchar, и если колонка объявлена как varchar, сервер приводит к типу параметра колонку, после чего индекс по ней бесполезен. Лечится сопоставлением через IsUnicode(false) и HasMaxLength, чтобы совпадали и кодировка, и длина.
Функции над колонкой. x.Name.ToLower() == value или x.CreatedAt.Date == today переводятся в вызов функции прямо в WHERE, и поиск по индексу отпадает. Регистронезависимость в SQL Server обычно уже даёт сопоставление колонки, отдельный ToLower там лишний.
Сравнение с параметром, который может быть пустым. Провайдер дописывает к условию проверку на NULL через OR, и такое условие тоже уводит в сканирование. Помогает ветвление в коде на два разных запроса.
Ширина выборки. Запрос сущности целиком тянет все колонки, покрывающий индекс не срабатывает, к каждой строке добавляется обращение к таблице. Проекция через Select в DTO нередко чинит план сама по себе.
Где спотыкаются
Рассуждают о плане, ни разу не увидев сгенерированный SQL. Включите логирование команд или пометьте запрос через TagWith, чтобы найти его в плане и в кэше.
Второе: винят индексы, когда причина в закэшированном плане под первый набор параметров. Симптом узнаваемый: один и тот же метод мгновенный для одного значения и мучительно медленный для другого.
Третье: обрывают запрос ранним AsEnumerable или ToList, а фильтруют уже в памяти. Индекс тут ни при чём, на клиент уехала вся таблица, и дальше работает не база, а сборщик мусора.