Mi sono appena imbattuto in problemi con prestazioni molto lente nell'ordinare la lista. Dopo aver eseguito una query sul database, ottengo il risultato e devo ordinarlo 4 volte prima che soddisfi i requisiti. La query viene eseguita abbastanza velocemente e ottengo quasi immediatamente tutti i risultati, tuttavia l'ordinamento dei record richiede quasi 8 secondi.
Sto anche usando paginazione quindi ogni volta che seleziono solo 50 record per pagina ma devo riordinare l'intero elenco ancora e ancora che è un incubo. Ragazzi, c'è un modo per farlo funzionare più velocemente?
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();
Penso che la causa dei tuoi problemi sia perché ricevi sottotitoli o sequenza e ordini questo sottoinsieme. Lo fai più volte e decidi di creare liste di tutti i risultati intermedi.
Per prima cosa vediamo come vuoi ordinare i tuoi studenti.
Quindi hai una schoolId
e una sequenza di Students
. Ogni Student
ha proprietà SchoolId
, Message
e Status
Si prende tutti Students
dalla scuola con schoolId
, e per qualche motivo si decide di chiamare questi studenti studentMessages
.
Quindi vuoi ordinare questi Students
(messaggi degli Students
) nel seguente ordine:
UserId
discendente User.Id
discendente (sicuramente non intendevi UserId
? Penso che sarebbe lo stesso) In una tabella:
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
Uno dei metodi sarebbe quello di lasciare che il sistema di gestione del database faccia ciò (AsQueryable). L'algoritmo di ordinamento sembra abbastanza complicato. Non sono sicuro che il DBMS possa farlo in modo più efficiente del tuo processo.
Un altro metodo potrebbe essere quello di recuperare solo gli Studenti di cui hai effettivamente bisogno e lasciare che il tuo processo esegua l'ordinamento (AsEnumerable). Fornire una classe che implementa IComparer<Student>
per decidere sull'ordine.
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);
Se lo studente ha molte proprietà che non verranno utilizzate dopo aver recuperato i dati, prendere in considerazione la creazione di una classe locale contenente solo le proprietà necessarie e limitare i dati selezionati a questa classe locale, ad esempio 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,
...
}
Naturalmente in tal caso, il tuo comparatore deve implementare IComparer<FetchedStudent>
.
Quindi creiamo uno StudentComparer
che StudentComparer
gli Studenti in base alle tue esigenze!
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
}
}
Si vede che il comparatore confronta costantemente se x.Message è uguale a null e se x.Status è uguale a notActive, per rilevare a quale gruppo di ordinamento x e y appartengono.
Prendi in considerazione la possibilità di creare una funzione che calcola solo una volta a quale gruppo di ordinamento appartiene uno Studente e ricorda il gruppo di ordinamento:
.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);
}
ecc. In questo modo il confronto con i messaggi e lo stato viene eseguito solo una volta
I problemi di prestazioni del codice sono molto probabilmente il risultato di un caricamento lento. Usando lo student
e la proprietà del message
(e nel caso della quarta query anche la proprietà user
), il database viene interrogato nuovamente per ogni riga. Più righe contiene lo studentMessage
, più lento sarà il codice. Questo è un cosiddetto problema "n + 1 SELECT". Per i dettagli, vedere questo link .
Se vuoi risolvere rapidamente il problema, devi affermare che anche le relative sottoentità vengono caricate con la prima richiesta. Per fare ciò, è necessario modificare la seguente riga e includere tutte le entità pertinenti:
var studentMessages = context.Students
.Where(s => s.SchoolId == SchoolId)
.ToList();
dovrebbe essere modificato in modo che includano anche il message
, l' user
e lo student
:
var studentMessages = context.Students
.Include(x => x.message)
.Include(x => x.student)
.Include(x => x.user)
.Where(s => s.SchoolId == SchoolId)
.ToList();
In questo modo, i dati vengono caricati con una richiesta al database e non più tardi.