Sto eseguendo il codice in questo modo:
var somethings = db.Somethings.Select(s => new SomethingViewModel
{
Id = s.Id,
Name = s.Name,
IsActive = s.IsActive
SubSomethings = s.SubSomethings.Select(ss => new SubSomethingViewModel
{
Id = ss.Id,
Name = ss.Name,
IsActive = ss.IsActive
}).Where(wss => wss.IsActive)
}).Where(ws => ws.IsActive && (ws.SubSomethings.Any())) //remove elements if no SubSomethings
.ToList();
Come puoi vedere, questa è una relazione uno a molti. C'è una lista di sottotemi in qualcosa. Se tolgo il && (ws.SubSomethings.Any ()), ricevo una lista molto veloce restituita.
Ma, voglio includere nella lista solo i Somethings che hanno almeno un SubSomething. Ho anche provato quanto segue e ho ottenuto la stessa orribile efficienza:
var somethings = db.Somethings.Select(s => new SomethingViewModel
{
Id = s.Id,
Name = s.Name,
IsActive = s.IsActive
SubSomethings = s.SubSomethings.Select(ss => new SubSomethingViewModel
{
Id = ss.Id,
Name = ss.Name,
IsActive = ss.IsActive
}).Where(wss => wss.IsActive)
}).Where(ws => ws.IsActive)
.ToList(); //this finishes very quickly
var somethings2 = somethings.Where(s => s.SubSomethings.Any()).ToList(); //This is where the code bogged down
Come posso riscrivere la mia query per ottenere il codice di bogging in modo che sia molto più veloce? Una cosa da notare: funziona perfettamente con uno o due record. Quando premo> 8000 record, ci vogliono almeno quattro minuti.
Ecco l'indice che ho creato sulla tabella SubSomething per la chiave esterna di SomethingId, che corrisponde a Something.Id
CREATE NONCLUSTERED INDEX [IX_SubSomething_SomethingId] ON [dbo].[SubSomething]
(
[SomethingId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO
Ecco la creazione della chiave esterna di SubSomething.SomethingId:
ALTER TABLE [dbo].[SubSomething] WITH CHECK ADD CONSTRAINT [FK_SubSomething_Something_SomethingId] FOREIGN KEY([SomethingId])
REFERENCES [dbo].[Something] ([Id])
GO
ALTER TABLE [dbo].[SubSomething] CHECK CONSTRAINT [FK_SubSomething_Something_SomethingId]
GO
EF Core è il tuo problema. Attualmente è noto l'esecuzione di subquery N + 1 quando la query contiene una sottoprotezione di raccolta.
L'unico modo per risolvere il problema e limitare il tutto a 2 query SQL è filtrare il più possibile, quindi caricare l'intera entità impostata con le entità secondarie in memoria utilizzando il caricamento ansioso, quindi passare a LINQ su Oggetti e fare la proiezione finale / filtraggio:
var somethings = db.Somethings
.Include(s => s.SubSomethings)
.Where(s => s.IsActive)
.AsEnumerable()
.Select(s => new SomethingViewModel
{
Id = s.Id,
Name = s.Name,
IsActive = s.IsActive,
SubSomethings = s.SubSomethings.Select(ss => new SubSomethingViewModel
{
Id = ss.Id,
Name = ss.Name,
IsActive = ss.IsActive
}).Where(wss => wss.IsActive).ToList()
})
.Where(s => s.SubSomethings.Any())
.ToList();
Come ha detto Ivan Stoev, EF Core è il tuo problema, condivido un'alternativa che non usa LINQ to SQL, ma che masticare malamente prestazioni migliori:
Utilizzare db.Database.SqlQuery ("Query") per interrogare la vista e restituire i dati:
var results = db.Database.SqlQuery <SomethingViewModel> ("Seleziona ColA, ColB, ColC da ViewSomething")
L'oggetto SomethingViewModel deve essere di tipo forte con propreties che corrispondono a colonne della vista.
So che non è bello e ho la query hardcoded per la vista, ma in generale è la prestazione più veloce che si ottiene usando EF da quando si passa al compilatore.