Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Méthode la plus rapide pour les insertions, les mises à jour et les sélections de SQL Server

Cette réponse se concentre principalement sur les opérations 'select' vs update/create/delete. Je pense qu'il est plus rare de mettre à jour plus d'un ou quelques enregistrements à la fois, et je pense donc aussi que "sélectionner" est l'endroit où les goulots d'étranglement ont tendance à se produire. Cela dit, vous devez connaître votre candidature (profil). Le meilleur endroit pour concentrer votre temps d'optimisation est presque toujours au niveau de la base de données dans les requêtes elles-mêmes, plutôt qu'au niveau du code client. Le code client n'est que de la plomberie :ce n'est pas la force principale de votre application. Cependant, comme la plomberie a tendance à être réutilisée dans de nombreuses applications différentes, je sympathise avec le désir de l'obtenir aussi proche que possible de l'optimum, et j'ai donc beaucoup à dire sur la façon de construire ce code.

J'ai une méthode générique pour sélectionner des requêtes/procédures dans ma couche de données qui ressemble à ceci :

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

Et cela me permet d'écrire des méthodes de couche de données publiques qui utilisent des méthodes anonymes pour ajouter les paramètres. Le code affiché fonctionne avec .Net 2.0+, mais peut être écrit encore plus court en utilisant .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;
       }
     );
}

Je vais m'arrêter ici pour pouvoir vous rediriger vers le code juste au-dessus qui utilise la méthode anonyme pour la création de paramètres.

C'est un code très propre, en ce sens qu'il place la définition de la requête et la création des paramètres au même endroit tout en vous permettant d'abstraire le code standard de connexion/appel de la base de données vers un endroit plus réutilisable. Je ne pense pas que cette technique soit couverte par l'un des points de votre question, et il se trouve qu'elle est également sacrément rapide. Je pense que cela couvre l'essentiel de votre question.

Je veux continuer, cependant, à expliquer comment tout cela s'emboîte. Le reste est assez simple, mais il est également facile de jeter cela dans une liste ou similaire et de se tromper, ce qui finit par nuire aux performances. Pour continuer, la couche métier utilise ensuite une usine pour traduire les résultats de la requête en objets (c# 3.0 ou version ultérieure) :

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]
            //...
        };
    }
}

Plutôt que de les avoir dans leur classe, vous pouvez également les regrouper dans une classe statique spécifiquement destinée à contenir les méthodes d'usine.

Je dois apporter une modification à la méthode de récupération d'origine. Cette méthode "donne" le même objet encore et encore, et cela ne fonctionne pas toujours aussi bien. Ce que nous voulons faire différemment pour que cela fonctionne, c'est de forcer une copie de l'objet représenté par l'enregistrement actuel, de sorte que lorsque le lecteur mute pour l'enregistrement suivant, nous travaillons avec des données propres. J'ai attendu après avoir montré la méthode d'usine pour que nous puissions l'utiliser dans le code final. La nouvelle méthode Retrieve ressemble à ceci :

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

Et maintenant, nous appellerions cette nouvelle méthode Retrieve() comme ceci :

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

Évidemment, cette dernière méthode peut être étendue pour inclure toute logique métier supplémentaire nécessaire. Il s'avère également que ce code est exceptionnellement rapide, car il tire parti des fonctionnalités d'évaluation paresseuses de IEnumerable. L'inconvénient est qu'il a tendance à créer de nombreux objets de courte durée, ce qui peut nuire aux performances transactionnelles dont vous avez parlé. Pour contourner ce problème, je casse parfois le bon niveau n et transmet les objets IDataRecord directement au niveau de présentation et évite la création d'objets inutiles pour les enregistrements qui sont simplement liés à un contrôle de grille tout de suite.

Le code de mise à jour/création est similaire, à la différence que vous ne modifiez généralement qu'un seul enregistrement à la fois plutôt que plusieurs.

Ou, je pourrais vous épargner la lecture de ce long article et vous dire simplement d'utiliser Entity Framework ;)