Dove dovrei inserire il metodo di accesso al database in un ambiente con vista ad albero

0

Ho scritto una classe per rappresentare i dati di tipo "Treeview", che possono essere semplificati come:

public class Item
{
    public string Name { get; set; }
    public KPI AssociatedKPI { get; set; }
    public List<Item> Children { get; set; }
}

A ogni 'ramo' dell'albero, può essere associato un KPI, che può essere semplificato come:

public class KPI
{
    public string Name { get; set; }
    public double Value { get; set; }
}

Il mio database sembra

+------+-------+
+ Name + Value +
+------+-------+
+ foo  +   5   +
+ bar  +  4.56 +
+------+-------+

La struttura ad albero, con i nomi dei KPI, dovrebbe essere già compilata.

Sto facendo fatica a decidere dove dovrei mettere il metodo per riempire i valori dei KPI.

  • Prima soluzione: nella classe KPI

SELECT value FROM KPI WHERE NAME = 'foo'

Sembra che rispetti l'SRP, la classe KPI è responsabile dei valori del KPI.

Tuttavia, ogni KPI non è a conoscenza dell'esistenza di altri, e ho bisogno di avere il maggior numero di domande quante ne ho nel mio albero.

  • Seconda soluzione: nella classe Articolo

Vorrei ottenere i nomi di tutti gli indicatori KPI nei discendenti di questo Item e ottenere una query simile a

SELECT Value FROM KPI WHERE Name IN ('foo', 'bar')

In questo modo, ho solo bisogno di uno per root dell'albero (quindi di solito, uno solo).

La mia preoccupazione è che si rompa SRP, il Item non dovrebbe essere incaricato di impostare i valori, dovrebbe?

In conclusione:

Quale sarebbe il modo migliore per farlo? C'è un terzo modo migliore a cui non ho pensato?

In questo caso, le prestazioni non sono davvero un problema, la tabella e l'albero sono piccoli, ma sto imparando e vorrei conoscere le migliori pratiche.

Grazie,

    
posta Maxime 21.11.2017 - 14:50
fonte

2 risposte

1

Credo che tu abbia frainteso l'SRP, o almeno lo stia applicando solo parzialmente.

  • First solution: In the KPI class

It seems like it respects the SRP, the KPI class is responsible for the values of the KPI.

Qui ci sono due responsabilità: recuperare il valore e archiviare il valore (in memoria).

"È correlato al KPI" non è una descrizione accurata di una responsabilità. Le responsabilità sono generalmente espresse come verbi: memorizzazione dei dati, registrazione dei messaggi, stampa dell'output, ordinamento dell'elenco, formattazione della data, ...

  • Second solution: In the Item class

La mia preoccupazione è che si rompa SRP, l'elemento non dovrebbe essere incaricato di impostare i valori, dovrebbe?

Sono d'accordo con te qui, Item non è sicuramente dove dovresti metterlo. Ma il tuo ragionamento è in qualche modo imperfetto (allo stesso modo del precedente punto elenco)

What would be the best way of doing it? Is there a third, better way I did not think about?
In this case, performance is not really an issue, the table and the tree are small, but I am learning, and would like to learn about the best practices.

Nell'interesse dell'apprendimento, terrò la risposta dimostrativa ma non implementata a livello di esperti di livello professionale.

Inoltre, non hai specificato come vuoi inserire i dati e vorrei che la mia risposta si concentri sull'intenzione del codice, piuttosto che sull'implementazione. Quindi mi concentrerò principalmente sulla struttura e sul risultato, piuttosto che sul codice specifico.

Prima di tutto

Un piccolo reframing filosofico.

Hai lasciato qualche lacuna nella tua domanda, ad es. come sono collegati gli oggetti o quale è lo scopo principale del sistema. Sto compilando alcuni spazi vuoti qui sulla base dell'esperienza e dell'inferenza.

A quanto ho capito, Item e KPI sono due parti della stessa entità. KPI è la carne e le patate effettive, il valore dell'oggetto. Item , d'altra parte, è semplicemente un wrapper che ti aiuta a connettere valori di KPI tra loro (in una struttura gerarchica).

A meno che tu non abbia omesso una parte importante (o ci sto riflettendo sopra), non vedo alcun motivo per Item e KPI per essere due classi separate. Per il resto della risposta (a meno che tu non possa spiegare perché questo non è corretto), ho intenzione di unirli in una singola classe:

public class KpiItem
{
    public string Name { get; set; }
    public double Value { get; set; }

    public List<KpiItem> Children { get; set; }

    // Personally, I prefer also having a link to the parent. 
    // But this is optional.
    public KpiItem Parent { get; set; }
}

In secondo luogo, in base ai dati della tabella, non vi è alcuna spiegazione su come viene archiviata la gerarchia degli elementi. Forse questo è memorizzato in un'altra tabella (ad esempio Items ) che non ci hai mostrato. Forse semplicemente non hai ancora sviluppato quella parte.

Qualunque sia la ragione, dobbiamo sapere come sono collegati prima di poterli recuperare con la gerarchia corretta. Quindi aggiungo una colonna alla tua tabella: il nome del genitore.

+------+-------+-------------------+
+ Name + Value +  Parent_Name      +
+------+-------+-------------------+
+ foo  +   5   +  (null)           +
+ bar  +  4.56 +  foo              +
+------+-------+-------------------+

In altre parole: bar è un figlio di foo . foo non è figlio di nessuno, è un elemento di primo livello.

Terza soluzione: una classe separata creata per gestire il recupero dei dati .

Questo è un modo molto più SRP di affrontare il problema.

Ora, non abbiamo ancora stabilito cosa vuoi recuperare:

  • Un singolo elemento (per nome)
  • Un singolo elemento (per nome) più i suoi figli diretti
  • Un singolo elemento (per nome) più tutti i suoi discendenti (figli, nipoti, pronipoti, ...)

A scopo dimostrativo, mostrerò un modo per recuperare un singolo elemento (per nome) più i suoi figli diretti. Se vuoi ottenere tutti i discendenti, è semplice implementare la ricorsione (a meno che il tuo set di dati non abbia riferimenti circolari).

Dato che stai utilizzando query SQL, anch'io. Avremo bisogno di due query:

  • Recupero di un articolo in base al suo nome.
  • Recupero di un elemento basato sul suo nome del genitore .

Le query sono relativamente semplici:

SELECT * FROM KPIITEMS WHERE NAME = 'foo'

SELECT * FROM KPIITEMS WHERE PARENT_NAME = 'foo'

Sidenote
Notice that I'm doing SELECT * instead of SELECT value. For your current example, you already know the name you're looking for, so you're only interested in the value.
But this quickly falls apart when you start looking for other things, e.g. the first alphabetical item, the item with the longest name, ... Because now, you don't know the name.

Generally, you'll want to create your KpiItem based on the returned query; not based on the filters you supplied. While one could logically explain the other; this only applies in rare cases and is not a good general approach. This is why I did SELECT *, so that the query returns all columns so we can fully populate the KpiItem.

Poiché la domanda non si concentra su come recuperare i dati da SQL, ho intenzione di ometterlo per semplicità. Supponiamo che esistano questi metodi:

public KpiItem GetItem(string query);
public List<KpiItem> GetItems(string query);

In altre parole, se forniamo la query corretta a questo metodo, ci fornisce gli oggetti necessari.

Un'implementazione di esempio di questa terza classe sarebbe la seguente:

public class DataFetcher
{
    public KpiItem GetKpiItemWithChildren(string name)
    {
        var theItem = GetKpiItem_ByName(name);

        theItem.Children = GetKpiItemsBy_ParentName(name);

        return theItem;
    }

    private KpiItem GetKpiItem_ByName(string name)
    {
        return GetItem("SELECT * FROM KPIITEMS WHERE NAME = '" + name + "'");
    }

    private List<KpiItem> GetKpiItems_ByParentName(string parentName)
    {
        return GetItems("SELECT * FROM KPIITEMS WHERE PARENT_NAME = '" + parentName + "'");
    }
}

E quindi puoi usare questa classe come segue:

var fetcher = new DataFetcher();

var fooItem = fetcher.GetKpiItemWithChildren("foo");

Sidenote
There are things missing for this example to be a properly usable (professional) piece of code. Most importantly, this does not protect against SQL injection.
You really should protect against SQL injection (read up on it when you're ready), but I've omitted it from the current example because (a) as a beginner you're likely not at that point yet and (b) I'm trying to keep the example as simple as possible so you can understand the intended structure.

Il tuo esempio è semplice. Una volta entrati nella creazione di applicazioni con molte tabelle e strutture dati notevolmente più complesse, questa risposta non si applica più. Ma suppongo che tu stia attualmente cercando una spiegazione per principianti, non tanto per un'implementazione a livello di esperti.

Se sei interessato a scalare verso l'alto, un buon punto di partenza è la lettura su Entity Framework e LINQ . Ma non c'è bisogno di affrettarsi, c'è il merito di concentrarsi prima sulle basi (come si dice, solo gli stupidi si affrettano a).

    
risposta data 21.02.2018 - 13:02
fonte
0

Non si fa menzione di dove KPI s ottiene i loro nomi e come

In ogni caso, si tratta di quei dati

  • se KPI s conosce già il proprio nome, anche il genitore Item può e deve recuperare l'elenco dei nomi dallo stesso provider. In questa configurazione Item dovrebbe essere responsabile del provisioning child KPI s con i dati, purché non vogliamo introdurre ItemKPIProvider ancora

  • se si tratta di un provider locale, o semplicemente non vogliamo che Item abbia qualcosa a che fare con l'origine dati name di suo figlio KPI s, possiamo ancora passare un lambda a ogni KPI costruttore per raccogliere i nomi in un array. È anche un po 'migliore in quanto non richiederà la ricorsione se abbiamo bisogno di ridimensionarla a% multidimensionaliItem.List s

Inoltre,

  • A giudicare dagli utenti pubblici, l'interfaccia potrebbe essere in violazione con sia D che I in SOLID
  • Considerare l'SRP senza il resto dei principi non è di per sé molto utile. Infatti, OOD diventerà molto intrecciato senza un modello fisico o convenzioni comprovate
  • In generale, la forma segue la funzione, quindi imporre limiti architettonici troppo precoci ostacolerà la possibilità di sperimentare e prototipare
risposta data 23.11.2017 - 01:03
fonte