Esportare i risultati di un'aggregazione MongoDb in ASP.NET Core

di Moreno Gentili, in ASP.NET MVC,

Nel precedente script (https://www.aspitalia.com/script/1268/Analisi-Dati-Aggregation-Framework-MongoDB.aspx) abbiamo esaminato alcune delle operazioni che possiamo usare in una pipeline di Aggregation Framework di MongoDB.

Ci sono situazioni in cui la nostra query di aggregazione è talmente complessa o coinvolge così tante collezioni da richiedere svariati secondi per essere completata. Questo periodo di attesa avrà un effetto negativo sulla navigazione dei nostri utenti, nel momento in cui desiderano visualizzare i risultati di tale aggregazione.

Fortunatamente, MongoDB ci permette di "esportare" i risultati della query di aggregazione in una propria collezione che, analogamente ad una cache, ci consentirà di recuperarli velocemente in ogni successiva query.

Per far questo, usiamo il metodo Out che deve necessariamente essere usato come ultima operazione nella pipeline.

_db.GetCollection<Restaurant>("restaurants")
  .Aggregate()
  //Qui operazioni di aggregazione, come illustrato nello script precedente
  //E, per ultima, l'invocazione al metodo Out in cui indichiamo il nome della
  //collection di destinazione, in cui saranno esportati i risultati
  .Out("aggregationResults");

Se stiamo realizzando un'applicazione ASP.NET Core MVC che visualizza i risultati dell'aggregazione, dobbiamo tener presente che:

  • Più utenti potrebbero visitare la pagina contemporaneamente ma questo non deve scatenare esportazioni concorrenti dei dati, che causerebbero un errore;
  • E' importante che la query di aggregazione venga rieseguita dopo un certo lasso di tempo, affinché gli utenti possano visualizzare dati freschi.

Per ottemperare ad entrambi questi requisiti, possiamo sfruttare il servizio di cache di ASP.NET Core che, grazie al suo meccanismo di scadenza delle chiavi, ci consentirà di capire quando è il momento di rieseguire l'esportazione.

Iniziamo configurando il servizio di cache dalla classe Startup. All'interno del suo metodo ConfigureServices aggiungiamo:

services.AddMemoryCache();

All'interno dello stesso metodo, configuriamo anche i servizi IMongoClient e IMongoDatabase del MongoDb.Driver come illustrato in questo precedente script:
https://www.aspitalia.com/script/1265/Utilizzare-MongoDB-ASP.NET-Core.aspx

Il nostro metodo ConfigureServices apparirà dunque così:

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc();
  services.AddMemoryCache();
  services.AddSingleton<IMongoClient>(x => new MongoClient(Configuration.GetConnectionString("mongo")));
  services.AddTransient<IMongoDatabase>(x => x.GetService<IMongoClient>().GetDatabase("test"));
}

Ora creiamo un Controller per ASP.NET Core MVC e l'action che si occuperà di visualizzare i risultati della query di aggregazione.

public class AggregationResultsController : Controller
{
  private readonly IMongoDatabase _db;
  private readonly IMemoryCache _cache;
  public AggregationResultsController(IMongoDatabase db, IMemoryCache cache)
  {
    _db = db;
    _cache = cache;
  }
  
  //Questa action supporta la paginazione dei risultati aggregati
  public async Task<IActionResult> Index(int page)
  {
    //Assicuriamoci che non sia stata fornita una pagina inferiore ad 1
    page = Math.Max(1, page);

    //Impostiamo il numero dei documenti da visualizzare per pagina
    const int pageSize = 10;

    //Otteniamo il nome della collection che contiene i risultati dell'aggregazione
    var aggregationResultsCollectionName = 
      GetAggregationResultsCollectionName(_db.GetCollection<Restaurant>("restaurants"));

    //Usiamo i metodi Find, Skip e Limit per ottenere una pagina dei risultati di aggregazione
    var results = await _db.GetCollection<Result>(aggregationResultsCollectionName)
      .Find(FilterDefinition<Result>.Empty)
      .Skip(pageSize*(page-1))
      .Limit(pageSize)
      .ToListAsync();

    //Visualizziamo i risultati nella view
    return View(results);
  }
  
  //Questo metodo si occupa di eseguire la query di aggregazione ed esportare i risultati
  private string GetAggregationResultsCollectionName(IMongoCollection<Restaurant> collection)
  {
    //Sfruttando il meccanismo di scadenza delle chiavi cache, possiamo
    //fare in modo che la query di aggregazione venga eseguita al massimo
    //una volta ogni 10 minuti
    TimeSpan refreshAggregationResultsEvery = TimeSpan.FromMinutes(10);

  //Nome della collection in cui esporteremo i risultati grazie al metodo Out
    string aggregationResultsCollectionName = "restaurantsAggregated";

    //Se la chiave cache non è presente o è scaduta, la query di aggregazione
  //verrà eseguita ed esporterà i risultati nell'apposita collection
    return _cache.GetOrCreate(aggregationResultsCollectionName, entry =>
    {
      //Impostiamo la scadenza della chiave cache
      entry.AbsoluteExpiration = DateTimeOffset.Now.Add(refreshAggregationResultsEvery);

      //Usiamo la classe Lazy che ci garantisce che due o più thread concorrenti non vadano
      //ad eseguire contemporaneamente l'esportazione della query di aggregazione
      return new Lazy<string>(() =>
      {
        collection.Aggregate()
          //Qui le operazioni di aggregazione
          //Poi usiamo il metodo Out per esportare i risultati 
          .Out(aggregationResultsCollectionName);

        return aggregationResultsCollectionName;
      });
    }).Value;
  }
}

In questo esempio, sfruttiamo sia la cache di ASP.NET Core che la classe Lazy<T> per assicurarci che venga eseguita solo un'esportazione alla volta. Alla prima esecuzione di pagina, l'utente dovrà attendere qualche secondo che l'aggregazione e l'esportazione dei risultati siano completate. Dalla successiva richiesta in poi, il caricamento sarà molto rapido.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi