Как улучшить производительность списка заказов несколько раз после получения данных из базы данных - PullRequest
0 голосов
/ 12 февраля 2019

Я только что столкнулся с проблемами из-за очень низкой производительности при упорядочении списка.После выполнения запроса к базе данных я получаю результат, и мне нужно заказать его 4 раза, прежде чем он будет соответствовать требованиям.Запрос выполняется довольно быстро, и я получаю все результаты почти сразу, однако сортировка записей занимает почти 8 секунд.

Я также использую нумерацию страниц, поэтому каждый раз я выбираю только 50 записей на страницу, но мне приходится переупорядочивать всесписок снова и снова, который является кошмаром.Ребята, есть ли способ заставить его работать быстрее?

var studentMessages = context.Students
    .Where(s => s.SchoolId == SchoolId).ToList();
var sSorted = studentMessages
    .Where(x => x.message == null && x.student.Status != (int)StudentStatusEnum.NotActive)
    .OrderByDescending(x => x.student.UserId)
    .ToList();

sSorted = sSorted
    .Concat(studentMessages
        .Where(x => x.message != null && x.student.Status != (int)StudentStatusEnum.NotActive)
        .OrderBy(x => x.message.NextFollowUpDate)
        .ToList()
    ).ToList();

sSorted = sSorted
    .Concat(studentMessages
        .Where(x => x.message != null && x.student.Status == (int)StudentStatusEnum.NotActive)
        .OrderByDescending(x => x.message.NextFollowUpDate)
        .ToList()
    ).ToList();

sSorted = sSorted
    .Concat(studentMessages
    .Where(x => x.message == null && x.student.Status == (int)StudentStatusEnum.NotActive)
    .OrderByDescending(x => x.user.Id)
    .ToList()
    ).ToList();

var allStudents = (isSelectAll == true ? sSorted  : sSorted .Skip(skipNumber).Take(query.AmountEachPage)).ToList();

Ответы [ 2 ]

0 голосов
/ 12 февраля 2019

Я думаю, что причина ваших проблем в том, что вы получаете подмножества или вашу последовательность и заказываете это подмножество.Вы делаете это несколько раз и решаете составить списки всех промежуточных результатов.

Давайте сначала посмотрим, как вы хотите упорядочить своих учеников.

Итак, у вас есть schoolId и последовательностьStudents.Каждый Student обладает свойствами SchoolId, Message и Status

Вы берете все Students из школы с schoolId, и по какой-то причине вы решаете назвать этих учеников studentMessages.

Затем вы хотите заказать эти Students (studentmessages) в следующем порядке:

  • Сначала все учащиеся с нулевым сообщением и статусом, отличным от notActive, упорядочены по убыванию UserId
  • Затем все учащиеся с ненулевым сообщением и статусом, отличным от notActive, упорядочены по Message.NextFollowUpdate
  • Затем все учащиеся с ненулевым сообщением и статусом, равным notActive, упорядочены по сообщению.NextFollowUpdate
  • Наконец, все учащиеся с нулевым сообщением и статусом, равным notActive, упорядочены по убыванию User.Id (уверен, что вы не имели в виду UserId? Думаю, это будет то же самое)

В таблице:

group | Message |  Status      | Order by
  1   | == null | != notActive | descending UserId
  2   | != null | != notActive | ascending  message.NextFollowUpdate
  3   | != null | == notActive | descending message.NextFollowUpdate
  4   | == null | == notActive | ascending  UserId

Один из методов - позволить вашей системе управления базами данных сделать это (AsQueryable).Алгоритм упорядочения кажется довольно сложным.Я не уверен, может ли СУБД сделать это более эффективно, чем ваш процесс.

Другим методом будет выборка только тех студентов, которые вам действительно нужны, и позволить вашему процессу выполнять упорядочение (AsEnumerable).Предоставьте класс, который реализует IComparer<Student> для принятия решения о порядке.

int schoolId = ...
IComparer<Student> mySpecialStudentComparer = ...
var orderedStudents = dbContext.Students
    .Where(student => student.SchoolId == schoolId)
    .AsEnumerable()         // move the selected data to local process

    // now that the data is local, we can use our local Student Comparer
    .OrderBy(mySpecialStudentComparer);

Если у вашего ученика есть много свойств, которые вы не будете использовать после извлечения данных, рассмотрите возможность создания локального класса, который содержиттолько те свойства, которые вам нужны и ограничивают выбранные данные этим локальным классом, например FetchedStudent

    .Select(student => new FetchedStudent
    {
        // select only the properties you actually plan to use,

        // for the sorting we need at least the following:
        Message = student.Message,
        Status = student.Status
        UserId = student.UserId,

        // Select the other Student properties you plan to use, for example:
        Id = student.Id,
        Name = student.Name, 
        ...
    }

Конечно, в этом случае ваш компаратор должен реализовать IComparer<FetchedStudent>.

Итак, давайте создадим StudentComparer, который будет сортировать учеников в соответствии с вашими требованиями!

class StudentComparer : IComparer<FetchedStudent>
{
    private readonly IComparer<int> UserIdComparer = Comparer<int>.Default;
    private readonly IComparer<DateTime> nextFollowUpdateComparer =
                     Comparer<DateTime>.Default;

    public int CompareTo(FetchedStudent x, FetchedStudent y)
    {
        // TODO: decide what to do with null students: exception?
        // or return as smallest or largest

        // Case 1: check if x is in sorting group 1
        if (x.Message == null && x.Status == notActive)
        {
            // x is in sorting group 1
            if (y.Message == null && y.Status == notActive)
            {
                // x and y are in sorting group 1.
                // order by descending UserId
                return -UserIdComparer.CompareTo(x.UserId, y.UserId);
                // the minus sign is because of the descending
            }
            else
            {   // x is in group 1, y in group 2 / 3 / 4: x comes first
                return -1;
            }
        }

        // case 2: check if X is in sorting group 2
        else if (x.Message != null && x.Status != notActive)
        {   // x is in sorting group 2
            if (y.Message == null && y.Status != notActive)
            {   // x is in group 2; y is in group 1: x is larger than y
                return +1;
            }
            else if (y.Message == null && y.Status != notActive)
            {   // x and y both in group 2: order by descending nextFollowUpDate
                // minus sign is because descending
                return -nextFollowUpdateComparer.CompareTo(
                       x.Message.NextFollowUpdate,
                       y.Message.NextFollowUpdate);
            }
            else
            {   // x in group 2, y in 3 or 4: x comes first
                return -1;
            }
        }

        // case 3: check if X in sorting group 3
        else if (x.Message == null && x.Status != notActive)
        {
           ... etc, you'll know the drill by know
    }
}    

Возможное улучшение

Вы видите, что компаратор постоянно сравнивает, равен ли x.Message нуль и равен ли x.Status равно notActive, чтобы определить, к какой группе сортировки принадлежат x и y.

Попробуйте создать функцию, которая только один раз вычисляет, к какой группе сортировки принадлежит студент, и запомните группу сортировки:

.Select(student => new FetchedStudent
{
    SortingGroup = student.ToSortingGroup(),

    ... // other properties you need
}

public int CompareTo(FetchedStudent x, FetchedStudent y)
{
    switch (x.SortingGroup)
    {
        case 1:
           switch y.SortingGroup:
           {
               case 1: // x and y both in sorting group 1
                  return -UserIdComparer.CompareTo(x.UserId, y.UserId);

               default: // x in sorting group 1, y in 2 / 3 / 4: x smaller
                  return -1;
           }
        case 2:
           switch y.SortingGroup:
           {
               case 1: // x in sorting group 2; y in sorting group 1: x larger
                  return +1;
               case 2: // x and y both in sorting group 2
                  return -nextFollowUpdateComparer.CompareTo(
                       x.Message.NextFollowUpdate,
                       y.Message.NextFollowUpdate);
           }    

и т.д..Таким образом, сравнение с сообщением и статусом выполняется только один раз

0 голосов
/ 12 февраля 2019

Проблемы с производительностью Кодекса, скорее всего, являются результатом отложенной загрузки.Используя свойства student и message (а в случае четвертого запроса также свойство user), база данных снова запрашивается для каждой строки.Чем больше строк содержит studentMessage, тем медленнее будет работать код.Это так называемая проблема «n + 1 SELECT».Подробности см. По этой ссылке .

Если вы хотите быстро решить проблему, вы должны подтвердить, что соответствующие дочерние объекты также загружаются с первым запросом.Чтобы сделать это, вам нужно изменить следующую строку и включить все соответствующие объекты:

var studentMessages = context.Students
  .Where(s => s.SchoolId == SchoolId)
  .ToList();    

следует изменить так, чтобы объекты message, user и student также были включены:

var studentMessages = context.Students
  .Include(x => x.message)
  .Include(x => x.student)
  .Include(x => x.user)
  .Where(s => s.SchoolId == SchoolId)
  .ToList();    

Таким образом, данные загружаются в базу данных одним запросом, а не позже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...