从数据库中获取数据后如何多次提高订购列表的性能

时间:2019-02-12 07:53:33

标签: c# performance entity-framework linq entity-framework-6

我刚遇到订购列表的速度非常慢的问题。对数据库运行查询后,我得到了结果,需要对其进行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 :(得分:1)

代码的性能问题很可能是延迟加载的结果。通过使用studentmessage属性(在第四个查询的情况下,也使用user属性),再次为每一行查询数据库。 studentMessage包含的行越多,代码执行的速度就越慢。这是所谓的“ n + 1 SELECTs”问题。有关详细信息,请参见此link

如果要快速解决问题,则需要断言相关的子实体也随第一个请求一起加载。为此,您需要更改以下行并包括所有相关实体:

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

应进行更改,以便也包括实体messageuserstudent

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

通过这种方式,将数据与一个请求一起加载到数据库,而以后再加载。

答案 1 :(得分:1)

我认为造成问题的原因是因为您获得了子集或序列并订购了该子集。您做了几次,然后决定列出所有中间结果。

首先让我们看看您想如何订购学生。

因此,您有一个schoolId和一个Students序列。每个Student都有属性SchoolIdMessageStatus

您带着Students带走了所有schoolId的学校,由于某种原因,您决定称这些学生为studentMessages

然后,您要按以下顺序订购这些Students(学生信息):

  • 首先所有消息为空且状态不等于notActive的学生,按降序UserId
  • 然后按Message.NextFollowUpdate的顺序排列所有消息为非空且状态不等于notActive的学生
  • 然后所有非空消息且状态等于notActive的学生,按Message.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)。排序算法似乎相当复杂。我不确定DBMS是否可以比您的过程更有效。

另一种方法是只获取您实际需要的学生,然后让您的流程进行排序(AsE​​numerable)。提供一个实现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);

如果您的学生具有很多在获取数据后将不使用的属性,请考虑创建仅包含所需属性的本地类,并将所选数据限制为该本地类,例如{{1 }}

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

可能的改进

您会看到比较器不断比较x.Message是否等于null和x.Status是否等于notActive,以检测x和y属于哪个排序组。

考虑创建一个函数,该函数仅计算一次学生所属的排序组并记住该排序组:

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
    }
}    

等这样,与“消息”和“状态”的比较仅进行一次