Оптимизируйте Entity Framework Query, избегайте отложенной загрузки
У меня есть запрос linq, который занимает несколько секунд (~2,6 с) для запуска. Но я хочу уменьшить это до минимума.
Мне нужно только чтение, поэтому я включил поведение.AsNoTracking().
Я также проверил запрос без операторов include, но мои операции после запроса get еще больше замедлили его, поэтому я оставил include для оптимизации других моих операций.
Основная цель состоит в том, чтобы уменьшить разговоры в базе данных, для этого использовать операторы ToList(),Include.
Код:
var obj = _context.MyContextModel.AsNoTracking()
.Where(x => x.CategoryList.Model.Id == 1)
.Where(x => x.CategoryList.Model.TypeId == 1)
.Where(x => x.Year.Select(y=>y.Datetime).Any(item => item.Year == 2010))
.Include(x => x.LinkedMarket).AsNoTracking()
.Include(x => x.Year).AsNoTracking()
.Include(x => x.CategoryList).AsNoTracking()
.Include(x => x.CategoryList.Model).AsNoTracking();
return obj.AsParallel().ToList();
Эта операция обычно возвращает около 1000-2000 записей MyContextModel, не включая "включенные"
Как я могу оптимизировать это дальше? Должен ли я загружать объекты в контейнерный класс? или другое решение?
Обновить
_context.Configuration.ProxyCreationEnabled = false;
_context.Configuration.LazyLoadingEnabled = false;
var obj = _context.MyContextModel.AsNoTracking()
.Where(x => x.CategoryList.Model.Id == 1)
.Where(x => x.CategoryList.Model.TypeId == 1)
.Where(x => x.LinkedMarket.FirstOrDefault(mar=>mar.MarketID == marketId) != null)
.Include(x => x.Year).AsNoTracking()
.Include(x => x.CategoryList).AsNoTracking()
.Include(x => x.CategoryList.Model).AsNoTracking();
return obj.AsParallel().ToList();
В основном я удалил предложение where, которое фильтрует год (я делаю это позже, для включения года), я добавил предложение Where, которое указывает рынок из getgo.
Я удалил Включить, который содержал рынок.
Одним из крупных воров производительности был Связанный рынок (я точно не знаю, почему что-то не понравилось EF).
Это уменьшило запрос примерно в среднем на 0,4 секунды. И вся операция установлена с 4+ секунд до потрясающих 0,7 секунд.
1 ответ
Каждое включение, которое вы делаете, в конечном итоге приводит к выполнению соединения в БД. Предположим, что ваша левая таблица имеет большой размер 1024 байта в размере записи, и что у вас есть много деталей, скажем 1000, и что размер записи подробностей составляет всего 100. Это приведет к тому, что информация для левой таблицы будет повторяться 1000 раз, эта информация БД будет подключен к проводу, а EF должен отфильтровать дубликат, чтобы создать ваш левый экземпляр.
Лучше не использовать include и выполнять явную загрузку. В основном выполняется 2 запроса в одном контексте.
У меня есть пример, использующий этот принцип ниже. Это может быть до 10 раз быстрее, чем полагаться на включение. (Кстати, БД может эффективно обрабатывать только ограниченное число объединений)
var adressen = adresRepository
.Query(r => r.RelatieId == relatieId)
.Include(i => i.AdresType)
.Select().ToList();
var adresids = (from a in adressen select a.AdresId).ToList();
IRepositoryAsync<Comm> commRepository = unitOfWork.RepositoryAsync<Comm>();
var comms = commRepository
.Query(c => adresids.Contains(c.AdresId))
.Include(i => i.CommType)
.Select();
Для commType и adresType я использую include, потому что существует отношение 1 к 1, я избегаю слишком большого количества соединений, и поэтому мои множественные запросы будут выполняться быстрее, чем один, использующий include. Я не включаю Comms в первый запрос, чтобы попытаться избежать второго запроса, суть в том, что в этом случае 2 запроса выполняются быстрее, чем один.
Суть в том, что есть нечто большее, чем просто избегать ленивых нагрузок, необходимо также учитывать, какие из них необходимы, а какие нет. Вам может понадобиться эта информация, и включение выполняется быстро и просто, но дополнительный запрос в том же контексте может быть быстрее.