Questa risposta si concentra principalmente sulle operazioni di "selezione" rispetto a quelle di aggiornamento/creazione/eliminazione. Penso che sia più raro aggiornare più di uno o pochi record alla volta, quindi penso anche che "seleziona" sia il punto in cui tendono a verificarsi i colli di bottiglia. Detto questo, devi conoscere la tua applicazione (profilo). Il posto migliore per concentrare il tempo di ottimizzazione è quasi sempre a livello di database nelle query stesse, piuttosto che nel codice client. Il codice client è solo l'impianto idraulico:non è la forza principale della tua app. Tuttavia, poiché l'impianto idraulico tende a essere riutilizzato in molte app diverse, sono d'accordo con il desiderio di avvicinarlo il più possibile all'ottimale, e quindi ho molto da dire su come costruire quel codice.
Ho un metodo generico per selezionare query/procedure nel mio livello dati che assomiglia a questo:
private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
//ConnectionString is a private static property in the data layer
// You can implement it to read from a config file or elsewhere
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
rdr.Close();
}
}
}
E questo mi consente di scrivere metodi di livello dati pubblici che utilizzano metodi anonimi per aggiungere i parametri. Il codice mostrato funziona con .Net 2.0+, ma può essere scritto anche più breve usando .Net 3.5:
public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead of a full sql query
return Retrieve(
@"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}
Mi fermo qui in modo da poterti indirizzare di nuovo al codice appena sopra che utilizza il metodo anonimo per la creazione dei parametri.
Questo è un codice molto pulito, in quanto mette la definizione della query e la creazione dei parametri nello stesso posto, consentendo comunque di astrarre la connessione al database standard/il codice di chiamata in un posto più riutilizzabile. Non credo che questa tecnica sia coperta da nessuno dei punti elenco nella tua domanda, e sembra anche dannatamente veloce. Penso che questo copra il senso della tua domanda.
Voglio continuare, però, a spiegare come tutto questo combacia. Il resto è abbastanza semplice, ma è anche facile inserire questo in un elenco o qualcosa di simile e sbagliare, in definitiva danneggiando le prestazioni. Quindi, andando avanti, il livello aziendale utilizza una factory per tradurre i risultati delle query in oggetti (c# 3.0 o successivo):
public class Foo
{
//various normal properties and methods go here
public static Foo FooFactory(IDataRecord record)
{
return new Foo
{
Property1 = record[0],
Property2 = record[1]
//...
};
}
}
Invece di averli nella loro classe, potresti anche raggrupparli tutti insieme in una classe statica specificamente destinata a contenere i metodi factory.
Devo apportare una modifica al metodo di recupero originale. Quel metodo "produce" lo stesso oggetto più e più volte, e questo non funziona sempre così bene. Quello che vogliamo fare diversamente per farlo funzionare è forzare una copia dell'oggetto rappresentato dal record corrente, in modo che quando il lettore muta per il record successivo stiamo lavorando con dati puliti. Ho aspettato fino a dopo aver mostrato il metodo di fabbrica in modo da poterlo utilizzare nel codice finale. Il nuovo metodo Recupera si presenta così:
private static IEnumerable<T> Retrieve(Func<IDataRecord, T> factory,
string sql, Action<SqlParameterCollection> addParameters)
{
//ConnectionString is a private static property in the data layer
// You can implement it to read from a config file or elsewhere
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return factory(rdr);
rdr.Close();
}
}
}
E ora chiameremmo quel nuovo metodo Retrieve() in questo modo:
public IEnumerable<Foo> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead of a full sql query
return Retrieve(Foo.FooFactory,
@"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}
Ovviamente quest'ultimo metodo può essere ampliato per includere qualsiasi logica di business aggiuntiva necessaria. Si scopre inoltre che questo codice è eccezionalmente veloce, perché sfrutta le funzionalità di valutazione pigra di IEnumerable. Lo svantaggio è che tende a creare molti oggetti di breve durata e ciò può danneggiare le prestazioni transazionali di cui hai chiesto. Per aggirare questo problema, a volte rompo un buon livello n e passo gli oggetti IDataRecord direttamente al livello di presentazione ed evito la creazione di oggetti non necessari per i record che sono semplicemente collegati immediatamente a un controllo griglia.
Aggiorna/Crea codice è simile, con la differenza che di solito modifichi solo un record alla volta anziché molti.
Oppure, potrei salvarti dalla lettura di questo lungo post e dirti semplicemente di usare Entity Framework;)