#entity-framework #linq #entity-framework-core #query-optimization
#структура организации #linq #сущность-структура-ядро #оптимизация запросов
Вопрос:
Я работаю над веб-API asp .net core с EF core. Я написал этот запрос. Но для выполнения этого требуется 20-30 секунд.
У кого-нибудь есть идея улучшить этот запрос.
var hotels = await _context.Hotels .Where(i =gt; (i.DestinationCode == request.Destination)) .Select(i =gt; new HotelListHotelVm { Item1 = i.Item1, Item2 = i.Item2, Item3 = i.Item3, Item4Code = i.Item4Code, Item4Description = i.Item4.TypeDescription, Item5 = i.Item5.Select(x =gt; new HotelListHotelVm.HotelListItem5Vm { Code = x.Item5Code, Description = x.Item5.Description, }).Where(x =gt;(incomingItem5s.Length gt; 0 ) ? (incomingItem5s.Contains(x.Code)) : (x.Code != "")), Item6 = i.Item6.Select(x =gt; new HotelListHotelVm.HotelListHotelItem6Vm { Id = x.Id, Item6TypeCode = x.Item6TypeCode, Order = x.Order, Path = x.Path, VisualOrder = x.VisualOrder, }).Take(3), HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x =gt; new HotelListHotelVm.HotelListFacilityVm { Id = x.Id, FacilityGroupCode = x.FacilityGroupCode, HotelFacilityGroupDescription = x.FacilityGroup.Description, FacilityCode = x.FacilityCode }), }) .Where( i =gt; ((incomingItem4.Length gt; 0 ) ? (incomingItem4.Contains(i.Item4Code)) : (i.Item4Code != "")) ) .OrderByDescending(i =gt; i.Code) .PaginatedListAsync(request.PageNumber, request.PageSize); foreach( var item in hotels.Items){ foreach(var facility in item.HotelFacilities){ foreach( var fac in _context.Facilities){ if(facility.FacilityCode == fac.Code){ facility.HotelFacilityDescription = fac.Description; } } } }
Если я удалю этот код для каждого, выполнение запроса займет 8-10 секунд. Но мне нужны эти коды для каждого. Потому что мне нужна HotelFacilityDescription
Есть какие-либо предложения по оптимизации запроса ?
Отредактируйте i.Facilities
— модель
public class HotelFacility { // removed some public int FacilityCode { get; set; } public int FacilityGroupCode { get; set; } public FacilityGroup FacilityGroup { get; set; } public int HotelCode { get; set; } public Hotel Hotel { get; set; } } }
Комментарии:
1. к сожалению, мы используем версию 5.0
2. (1) Пожалуйста, укажите класс модели, на который указывает
i.Facilities
свойство навигации по коллекции, в вопросе. (2) Также объясните , чего вы пытаетесь достичь.ToList().Distinct()
, так как это не делает ничего полезного — идентификатор уже уникален, поэтому оператор distinct не изменяет результирующий набор, но может повлиять на производительность запроса в базе данных оптимизатор запросов недостаточно умен, чтобы игнорировать его.3. (1) Я обновил. (2) Потому что есть какой-то дубликат
FacilityCode
4. (2) Вы удалили из модели некоторые свойства, которые могут быть существенными. Запрос показывает , что должно быть вызвано свойство
Id
, которое, скорее всего, является PK, т. Е. уникальным, поэтому различие не имеет никакого эффекта. Distinct использует все свойства, а не толькоFacilityCode
. Вот почему я спросил, чего вы пытаетесь достичь — согласно вашему комментарию, это определенно не делает того, чего вы ожидаете.5. (1) Кроме того , разве у вас нет
public Facility Facility { get; set; }
навигационного свойства (как у вас есть дляFacilityGroup
)? Если нет, то почему бы и нет? Убедитесь, что он у вас есть, а затем просто используйте его внутри LINQ для запроса сущностей, напримерFacilityCode = x.FacilityCode, HotelFacilityDescription = x.Facility.Description
, и удалите цикл.
Ответ №1:
_context.Facilities
будет перечисляться (т. е. будет вызываться база данных) для каждой итерации предыдущих циклов. Быстрое решение состоит в том, чтобы назвать его единицами и сохранить результаты в переменной:
var facilities = _context.Facilities.ToList(); foreach( var item in hotels.Items){ foreach(var facility in item.HotelFacilities){ foreach(var fac in facilities){ if(facility.FacilityCode == fac.Code){ facility.HotelFacilityDescription = fac.Description; } } } }
Следующим улучшением может быть преобразование facilities
в Dictionary
для целей поиска.
Еще лучшим подходом может быть написание объединения запросов со _context.Facilities
стороны базы данных (но здесь требуется дополнительная информация).
Комментарии:
1. Спасибо @Guru. Есть ли какие-либо предложения по улучшению этого запроса
var hotels = await _context.Hotels.......
. Потому что, если я прокомментирую коды для каждого, остальная часть запроса также займет 2-8 секунд.2. @hanushic Трудно сказать. Проверьте сгенерированный SQL, проверьте план выполнения. Может быть, некоторые индексы помогут. Или изменение нумерации страниц, так
.OrderByDescending(i =gt; i.Code)
как при пропуске может быть очень дорогостоящим.3. Хорошо, я проверю. Еще раз спасибо, братан
Ответ №2:
Я читал это пару раз, но это похоже на отношения для отеля.Средства-это средства, так что не могли бы вы просто сделать:
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x =gt; new HotelListHotelVm.HotelListFacilityVm { Id = x.Id, FacilityGroupCode = x.FacilityGroupCode, HotelFacilityGroupDescription = x.FacilityGroup.Description, FacilityCode = x.FacilityCode, HotelFacilityDescription = x.Description }),
Если по какой-то причине отель.Объекты не указывают на Объект, но являются сущностью группы «Много ко многим» для группы объектов, которая также содержит код объекта, если связанная группа объектов имеет доступ к набору объектов под ней, вы можете использовать это:
Редактировать: Похоже, что несколько объектов используют один и тот же код, где некоторые могут иметь нулевое описание. При условии, что объекты, соответствующие коду, будут находиться в одной и той же группе объектов и не будут учитывать один и тот же код в разных группах объектов. Если вам нужно сопоставить код для всех объектов, то, вероятно, нет большой альтернативы загрузке всего набора кодов и описаний объектов.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x =gt; new HotelListHotelVm.HotelListFacilityVm { Id = x.Id, FacilityGroupCode = x.FacilityGroupCode, HotelFacilityGroupDescription = x.FacilityGroup.Description, FacilityCode = x.FacilityCode, HotelFacilityDescription = x.FacilityGroup.Facilities.Where(f =gt; f.Code == x.FacilityCode amp;amp; f.Description != null).Select(f =gt; f.Description).FirstOrDefault() }),
Это позволит избежать необходимости загружать все средства для решения этого кода. В противном случае, если вам действительно нужно выполнить выборку по всем объектам, лучше всего было бы предварительно загрузить их, но вместо того, чтобы извлекать весь объект объекта, я бы рекомендовал только нужные вам значения, код и описание. Это сокращает объем необходимой памяти и потенциально ускоряет выполнение запроса:
var facilities = _context.Facilities .Select(f =gt; new { f.Code, f.Description }).ToList();
Редактировать: Оттуда найдите совпадение, используя:
foreach( var facility in hotels.Items.SelectMany(x =gt; x.HotelFacilities) { facility.HotelFaciltyDescription = facilities .Where(x =gt; x.Code == facility.FacilityCode amp;amp; !string.IsNullOrEmpty(x.Description) .Select(x =gt; x.Description) .FirstOrDefault(); }
Я бы рекомендовал предложение OrderBy, чтобы гарантировать предсказуемость выбора объекта, поскольку похоже, что в коде с ненулевым описанием может быть несколько совпадений.
Комментарии:
1. ДА. FaciltyGroup поддерживает отношения с Facility. Но некоторые описания являются нулевыми. Поэтому я воспользовался Последним. Спасибо вам. Но все равно это занимает 8 секунд.
2. Могу ли я использовать, например, этот ` var facilitiesList = _context. Средства. ТоЛист(); `
HotelFacilities = hotelFacilitiesList.Where(x =gt; (x.HotelCode == i.Code)) .Select(x =gt; new HotelListHotelVm.HotelListFacilityVm { Id = x.Id, FacilityGroupCode = x.FacilityGroupCode, HotelFacilityGroupDescription = x.FacilityGroup.Description, FacilityCode = x.FacilityCode })
3. Я внес пару правок в примеры. В целом эти запросы будут дорогостоящими с такими вещами, как отдельные вызовы или запросы по целым таблицам для поиска слабо связанных или ненадежных значений.
Ответ №3:
Цикл может быть устранен путем проецирования значения в запросе LINQ to Entities.
Это было бы довольно просто, если бы у вас были свойства отношений и навигации, как у других *Code
полей. Но, как уточняется в комментариях, такой связи нет, поэтому вам придется прибегнуть к старому доброму руководству, оставленному другим соединением, чтобы эмулировать то, что автоматически предоставляет свойство навигации, например
HotelFacilities = i.Facilities.ToList().Distinct().Take(6) // left outer join with Facilities .SelectMany(x =gt; _context.Facilities .Where(f =gt; x.FacilityCode == f.Code).DefaultIfEmpty(), (x, x_Facility) =gt; new HotelListHotelVm.HotelListFacilityVm { Id = x.Id, FacilityGroupCode = x.FacilityGroupCode, HotelFacilityGroupDescription = x.FacilityGroup.Description, FacilityCode = x.FacilityCode, HotelFacilityDescription = x_Facility.Description // lt;-- }),
Здесь x_Facility
эмулируется необязательное свойство навигации по ссылкам x.Facility
, если оно существует.
В случае, если вам нужно только одно свойство из связанной таблицы, вместо соединения слева вы также можете использовать исходный запрос с одним значением, возвращающим коррелированный подзапрос внутри проекции, например
HotelFacilities = i.Facilities.ToList().Distinct().Take(6) .Select(x =gt; new HotelListHotelVm.HotelListFacilityVm { Id = x.Id, FacilityGroupCode = x.FacilityGroupCode, HotelFacilityGroupDescription = x.FacilityGroup.Description, FacilityCode = x.FacilityCode, HotelFacilityDescription = _context.Facilities .Where(f =gt; x.FacilityCode == f.Code) .Select(f =gt; f.Description) .FirstOrDefault() // lt;-- }),
или даже
HotelFacilityDescription = _context.Facilities .FirstOrDefault(f =gt; x.FacilityCode == f.Code).Description
Все это устранит необходимость выполнения цикла post дополнительных запросов к базе данных. Вы можете протестировать их и выбрать тот, который обладает наилучшей производительностью (#2 и #3 создают один и тот же SQL, так что это дело вкуса — выбор между #1 и #2/3).