Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Il metodo più veloce per inserimenti, aggiornamenti e selezioni di SQL Server

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;)