Negli scorsi script abbiamo introdotto l'utilizzo di MongoDB in ASP.NET Core e abbiamo visto come installare il driver ed eseguire le operazioni CRUD basilari. Uno dei grandi vantaggi del client per .NET Core è l'eccellente supporto a LINQ, che ci permette di eseguire query con una naturalezza estrema e senza dover conoscere praticamente nulla della sintassi di MongoDB.
Usare LINQ per eseguire query
Riprendiamo per esempio il controller che abbiamo già usato negli script precedenti, e modifichiamo la action Index in questo modo:
private readonly IMongoCollection<Person> _people; // GET: People public async Task<IActionResult> Index(string filter) { var query = _people.AsQueryable(); if (!string.IsNullOrWhiteSpace(filter)) query = query.Where(x => x.Name.ToLower().StartsWith(filter)); return View(await query.ToListAsync()); }
Nell'esempio in alto abbiamo aggiunto un parametro filter che, quando specificato, vogliamo usare per effettuare ricerche. Tutto ciò che dobbiamo fare è invocare l'extension method AsQueryable per poter iniziare a usare tutti i metodi LINQ che già conosciamo. Nel nostro caso, vogliamo cercare gli elementi che iniziano per la stringa di filtro, effettuando un ToLower per far sì che la ricerca sia case insensitive.
Indici in MongoDB
Tutto sembra estremamente facile e naturale. Il rovescio della medaglia, però, è che un utilizzo inconsapevole di questa tecnica possa portare a problemi prestazionali in produzione. Cerchiamo di capire questo concetto analizzando l'execution plan del server.
Per prima cosa, se facciamo il ToString della nostra variabile query, possiamo recuperare il comando che il driver eseguirà sul database. Nel nostro caso, sarà qualcosa di simile al seguente:
aggregate([{ "$match" : { "Name" : /^m/i } }]
che nel linguaggio di MongoDB si traduce in "recupera tutti i documenti la cui proprietà Name inizia per 'm', case insensitive".
Per determinare l'execution plan dobbiamo sfruttare la console di MongoDB, che possiamo richiamare con il comando
mongo.exe
o, se stiamo usando Docker, con
docker exec -it ..nomecontainer.. mongo
A questo punto, possiamo analizzare il comportamento della query digitando:
use MyTestDb db.people.aggregate([{ "$match" : { "Name" : /^m/i } }], { explain:true })
La prima riga seleziona il database su cui vogliamo operare, ossia MyTestDb. La seconda, invece, esegue l'operazione aggregate vista in precedenza, sulla collection people, passando il parametro opzionale explain a true.
Quest'ultimo farà sì che, invece dei risultati della query, ne venga stampato l'execution plan. Si tratta di un testo non troppo difficile da interpretare, il cui punto di maggiore interesse è il seguente:
"winningPlan" : { "stage" : "COLLSCAN", "filter" : { "Name" : { "$regex" : "^m", "$options" : "i" } }, "direction" : "forward" },
Come possiamo notare, la strategia scelta da MongoDB è COLLSCAN. Questo perchè, in assenza di un opportuno indice, l'unica opzione che il server ha è quella di scorrersi tutta la collection di documenti e valutarli uno per uno. Con pochi dati il problema non si nota, ma il rischio è che questa query diventi lentissima una volta che siamo in produzione.
La soluzione, allora, è quella di creare un indice, in questo caso sulla proprietà Name, che possa essere sfruttato per migliorare sensibilmente le prestazioni. Possiamo farlo con l'istruzione seguente:
db.people.createIndex({ Name:1 })
Il parametro "1" indica che l'indice è ordinato in senso crescente, cosa che non ha molto peso nel nostro esempio specifico.
Se a questo punto estraiamo di nuovo l'exeuction plan, il risultato ottenuto sarà molto diverso:
"winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "indexName" : "Name_1", "filter" : { "Name" : { "$regex" : "^m", "$options" : "i" } }, ...
Ora MongoDB sta effettivamente eseguendo una IXSCAN, ossia una scansione dell'indice Name_1, con ovvi vantaggi dal punto di vista delle prestazioni anche con grandi moli di dati.
Conclusioni
In questo script abbiamo visto come il driver .NET Core di MongoDB ci permetta di sfruttare LINQ per eseguire query sulla base dati. Tuttavia, in assenza di un opportuno indice, il query engine si troverà costretto a effettuare uno scan della intera collection, con un ovvio decadimento prestazionale.
Tramite la console, possiamo analizzare l'execution plan della query e creare indici che ne migliorino le performance.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Realizzare un oggetto DataList generico in Blazor
Inizializzazione asincrona di un servizio allo startup di un'applicazione Blazor
Avviare una registrazione audio e video in una applicazione della Universal Windows Platform
Effettuare il tracing asincrono delle chiamate a un'applicazione ASP.NET Core
Caricare le immagini di una pagina in lazy loading con Angular
Elencare gli utenti loggati con Blazor Server
Storage persistente con Kubernetes e AWS
Creare una libreria di Controller in ASP.NET Core Web API
Eseguire lo shutdown pulito di un'applicazione ASP.NET Core
Creare un interceptor per ottimizzare il codice SQL generato da Entity Framework Core
Abilitare e gestire il prerendering nelle applicazioni Blazor WebAssembly
I più letti di oggi
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Utilizzare gli eventi pubblicati da SaveChanges in Entity Framework Core
- Un confronto tra React, Angular, Vue.js e Svelte: Routing e HTTP api
- Recuperare la tipologia di contenuto di una stringa con la Universal Windows Platform
- Blazor Conference 2021 - Online