Как сузить выборку до нужных полей в таблице с большим количеством столбцов?
Что проверяют
Знаете ли вы, что тянуть все столбцы ради двух полей дорого, и умеете ли в C# сделать проекцию так, чтобы она дошла до SQL. Следом почти всегда спрашивают, какой SQL сгенерирует ваш LINQ-запрос и как это проверить.
Как отвечать
В SQL всё просто: перечислить нужные столбцы вместо SELECT *. Выигрыш не только в трафике: если столбцы покрыты индексом, в SQL Server через INCLUDE, база ответит из индекса, не читая строки таблицы.
В EF Core то же делается проекцией:
Selectв DTO или анонимный тип:context.Employees.Where(...).Select(e => new EmployeeListItem(e.Id, e.FirstName, e.LastName)). Пока этоIQueryable, проекция транслируется в SQL, и в запрос уходят только эти столбцы;- трекинг: DTO из проекции не попадает в change tracker, такие чтения дешевле по памяти;
- AutoMapper:
ProjectTo<T>()строит проекцию из конфигурации маппинга, в отличие отMapпосле загрузки; - Dapper: столбцы перечисляются в SQL явно и маппятся на класс по именам;
- проверка:
ToQueryString()показывает SQL без выполнения,LogToвыводит реальные запросы.
Если в таблице есть тяжёлые и редко нужные столбцы вроде nvarchar(max) или varbinary(max), их выносят в отдельную сущность через table splitting и грузят по требованию. Для обновления пары полей в EF Core 7 и новее есть ExecuteUpdate, без загрузки сущности.
Где спотыкаются
Вызывают ToList() или AsEnumerable() до Select. Проекция выполняется уже в памяти, а из базы пришли все столбцы.
Добавляют Include к запросу с проекцией и думают, что он что-то подгружает. Если сущность не попадает в итоговый Select, Include игнорируется.
Возвращают из API сущность целиком «на всякий случай». Кроме лишних данных, это утечка полей, которые клиенту видеть не положено.