Вернется ли отслеживаемая сущность при использовании Select в EF Core?
Что проверяют
Знаете ли вы точное правило отслеживания, а не общее «проекция не отслеживается». Следом обычно дают запрос с анонимным типом, внутри которого лежит сама сущность, и просят сказать, что попадёт в ChangeTracker.
Как отвечать
Правило такое: в отслеживающем запросе EF Core ставит на учёт все экземпляры сущностей с ключом, которые он сам материализовал, где бы в результате они ни оказались. Тип результата значения не имеет.
- только скалярные поля:
Select(u => new { u.Id, u.Name })или проекция в DTO. Сущностей в результате нет, отслеживать нечего; - сущность внутри анонимного типа:
Select(b => new { Blog = b, Count = b.Posts.Count() }). ЭкземплярыBlogотслеживаются, обёртка ничего не отключает; - сущность из навигации: если в проекции есть
b.Posts.OrderBy(p => p.Date).LastOrDefault(), отслеживается иPost; - клиентский метод в финальной проекции: если EF материализует сущность, чтобы передать её в ваш метод, она тоже встаёт на учёт;
- keyless-сущности: не отслеживаются никогда.
Отключается это через AsNoTracking(), через AsNoTrackingWithIdentityResolution(), если одинаковые строки должны стать одним объектом, или глобально через QueryTrackingBehavior.NoTracking. Проверить просто: после запроса посмотреть context.ChangeTracker.Entries().
Практический вывод: для чтения проекция в DTO лучший вариант, из базы тянутся только нужные колонки, и трекер не тратит память. Для изменения данных загружайте сущность целиком отслеживающим запросом.
Где спотыкаются
Считают, что любой Select отключает трекинг, и удивляются, почему SaveChanges() сохранил правку объекта, вложенного в анонимный тип.
Создают сущность руками в проекции: Select(u => new User { Id = u.Id, Name = u.Name }). Такой объект не отслеживается, а если затем вызвать Update(), EF пометит изменёнными все свойства и затрёт незагруженные колонки значениями по умолчанию.
Забывают про стоимость трекинга. Большая выборка сущностей для отчёта без AsNoTracking() держит снимки в памяти и замедляет DetectChanges.