È meglio avere una singola query che restituisca tutte le entità correlate, o query separate per ogni caso?

2

Sommario

Quando si scrivono i metodi per interrogare le entità correlate, è meglio avere un unico metodo per tutte le entità correlate o metodi separati per ciascuna combinazione?

Problema completo

Sto scrivendo le classi di repository per un progetto .NET MVC in cui ciascun repository è responsabile per l'esecuzione di query per un tipo di entità specifico. Sto usando Dapper, quindi tutte le domande sono scritte a mano. Tutti gli repository implementano un'interfaccia IRepository<TEntity> , che ha metodo TEntity FindById(id) .

Recuperare solo l'entità è ovviamente molto semplice, ma mi trovo di fronte a un dilemma quando si tratta di andare a prendere i figli ei nipoti dell'entità e così via. Spesso, perché un'entità sia utilizzabile, ha bisogno di alcuni o tutti i suoi discendenti. Inizialmente ho scritto FindById per restituire l'entità con tutti i discendenti, che soddisfa tutti i casi d'uso, ma è anche dispendiosa in molti casi in cui ho bisogno solo di diversi discendenti.

Ho scritto i singoli metodi per il recupero di varie combinazioni. FindByIdWithChildA o FindByIdWithChildBAndGrandChildC e così via. Il problema qui è che i nomi dei metodi continuano a essere sempre più lunghi, il che li rende difficili da leggere e spreca molto spazio nel codice.

La mia prossima idea era di accettare vari valori di enum flag che designavano i discendenti da restituire. Questo ha funzionato bene per i bambini immediati, ma quando sono arrivato ai nipoti e oltre ho avuto tante combinazioni possibili da implementare, e molti di loro non avevano senso o non sarebbero stati usati.

Quando scrivo tutto questo testo, mi sembra quasi di scrivere un ORM cattivo invece di repository. Ciò ha certamente rallentato notevolmente lo sviluppo e si è distolto dall'effettiva implementazione di nuove funzionalità.

L'ovvia soluzione sbagliata è non utilizzare i join SQL e fare in modo che i repository uniscano le entità e le raccolte di entità usando LINQ. L'utilizzo di LINQ con EntityFramework potrebbe essere un'opzione, ma non credo che la soluzione debba passare a un'altra libreria.

Dovrei semplicemente semplificare e fare in modo che il singolo metodo / query restituisca tutto? In caso contrario, qual è il modo migliore per implementare concisamente metodi separati?

Questa risposta indica che un metodo di tipo FindByIdWithChild è normale, ma non posso immaginare che essere buona pratica per query complesse.

Chiarimento

Quindi le mie entità sono Sport, Divisione, SkillLevel e TeamType. Ogni sport ha un elenco di divisioni, SkillLevels e TeamTypes. Ogni divisione ha un singolo TeamType e un elenco di SkillLevels.

Supponiamo che voglio una divisione con il suo genitore Sport, il suo TeamType e tutti i suoi SkillLevels. Dovrei fare:

  1. DivisionRepository.FindById(id) che restituisce tutte queste proprietà, ignorando ogni caso in cui voglio solo una divisione o una divisione e sport, divisione e tipo di squadra, ecc.

  2. DivisionRepository.FindById(id, BitFlagQueryScope) che ha un grande casino di un'implementazione per risolvere tutti i possibili flag, alcuni dei quali potrebbero non essere necessari.

  3. DivisionRepository.FindByIdWithSportAndTeamTypeAndSkillLevels(id) e uno di questi metodi per ogni combinazione di cui ho bisogno.

In tutti e tre i casi, sto facendo un viaggio nel database. È solo questione di come strutturo il mio codice.

Esempio aggiuntivo, supponiamo che io voglia uno sport con solo i suoi TeamTypes. Altrove avrò bisogno di uno sport con i suoi SkillLevels, ecc. Stesse domande come sopra. Sto facendo solo un viaggio nel database, ma dovrei usare un codice diverso per queste diverse query, o semplicemente prendere tutto e scartare / non usare l'eccesso?

Chiarimento aggiuntivo

Nel caso ci sia qualcosa di sbagliato nel mio design in generale, ecco come sto usando le entità:

Action(string id)
{
    var sport = SportRepository.FindById(id); // This needs to either return everything associated with the entity or be changed to allow specification
    var model = new SportDisplayModel(sport);
    return View(model);
}

Ho due modelli, $ Entity $ DisplayModel e $ Entity $ EditModel, e li uso per ogni vista.

    
posta Michael Brandon Morris 02.10.2018 - 04:15
fonte

2 risposte

3

Se il server non si trova sulla stessa macchina fisica dell'applicazione che effettua la chiamata, la progettazione dovrebbe cercare di ridurre al minimo il numero totale di query, in particolare le query di piccole dimensioni, poiché la latenza della rete può richiedere un intervallo di tempo sproporzionato.

Storia vera: una volta ho trasformato un'attività di generazione di rapporti che richiedeva più di 24 ore per eseguirne una che sarebbe andata a finire in meno di 45 minuti e produrre gli stessi risultati, senza fare altro che ridurre drasticamente il numero di hits del database. Ecco quanto è significativa la latenza di accesso al database.

    
risposta data 02.10.2018 - 04:35
fonte
3

Il modo in cui riduci la latenza di rete è "smorzando" le tue query; cioè recuperando una porzione più ampia di dati su ogni richiesta. Un modo per farlo è trattare gli aggregati dei domini aziendali invece dei singoli record del database.

Invece di usare nomi di metodi generici come FindByIdWithChildBAndGrandChildC (che è solo una forma più sofisticata di un semplice modello di repository), crea oggetti di business che hanno un significato per l'azienda che dividono insieme i singoli record in un aggregato.

Ad esempio, invece di caricare InvoiceHeader , BillingAddress , ShippingAddress , List<LineItem> e così via separatamente, scrivi un oggetto Invoice che può recuperare tutti questi record ed esporre i dati come proprietà pubbliche . È possibile inserire il necessario IDBConnection nell'oggetto e incapsulare tutte le chiamate SQL e Dapper necessarie come richiesto.

Infine, se il caricamento lazy ti aiuterà, puoi simulare le query ritardate (e la memorizzazione nella cache) in questo modo:

private MyEntity _myEntity;

public MyEntity MyEntity
{ 
    get
    {
        if (_myEntity == null)
           _myEntity = dbConnection.Query<MyEntity>(sql, parameters).FirstOrDefault();

        return _myEntity;
    }
}
    
risposta data 02.10.2018 - 05:25
fonte

Leggi altre domande sui tag