Как оптимизировать ядро запроса — Ef

#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).