Sébastien Pertus
Créer une classe d'accès aux données avec ADO.Net 2.0
Accès générique Ado.Net 2.0
Par Sébastien Pertus publié le 14/05/2006 à 22:34, lu 9903 fois, 7 pages
 5 | Implémentation des différentes commandes
Téléchargez le code source - 60 Kb
Implémentation des différentes commandes
Notre classe est prête ; il ne reste plus qu'à coder les différentes commandes que vous avez l'habitude d'utiliser :
DbConnection
Il est tant d'implémenter une fonction capable de créer une connexion.Pour rappel, créer une connexion avec Ado.Net 1.1 (utilisons le provider Sql Server) revient à faire :
System.Data.SqlClient.SqlConnection myConnection = new SqlConnection(ConnectionString)
Avec notre fabrique ce code devient :
        public DbConnection GetConnection(String connectionString)
        {
            // Cration de la connection
            DbConnection conn = this.DbProviderFactory.CreateConnection();

            // Rcupration de la chaine de connexion
            connectionStringSettings = connectionStringsCollection[connectionStringName];

            // Affectation de la chaine de connexion
            conn.ConnectionString = connectionStringSettings.ConnectionString;

            // retour de la connexion
            return conn;

        }
L'instance correcte est retournée par notre Fabrique DbProivderFactory, qui nous retourne un objet dérivé de DbConnection : SqlConnection, OracleConnection etc ...
Notez que notre fonction ne renvoit pas une chaine de connexion de type SqlConnection mais de type DbConnection ( utile lorsque l'on veut faire une classe générique)

Erreur à ne pas commettre : Inutile ici d'ouvrir la connexion (conn.Open()) Nous ferons cette action le plus tard possible, juste avant l'éxécution d'une Command par exemple.

Nous allons pouvoir ainsi créer sur ce modèle, tous les objets nécessaires à notre classe et utile pour accéder aux données, quelque soit votre provider.
DbCommand
L'objet command est créé suivant le même principe que notre objet connexion.
Pour illustrer son utilisation et compléter notre classe, nous allons créer deux méthodes simples : ExecuteOrder et ExecuteScalar.
La première exécute un ordre sur votre base de donnée et ne renvoit pas de résultat (si ce n'est le nombre de lignes affectées)
La deuxième méthode renvoit un résultat unique.

Mais tout d'abord il nous faut créer une méthode renvoyant un objet DbCommand :
public DbCommand CreateDbCommand(String commandText, CommandType commandType)
 {
     DbCommand command = this.DbProviderFactory.CreateCommand();
     command.CommandType = commandType;
     command.CommandText = commandText;
     command.Connection = GetConnection();

     return command;

 }
Il ne reste plus qu'à coder nos deux fonctions :
/// <summary>
/// Excute un ordre T-SQL et renvoit le nombre de lignes affectes
/// </summary>
public int ExecuteNonQuery(String commandText)
{
 int resultat;
 DbConnection conn = null;
 try
 {
  conn = this.GetConnection();
  DbCommand command = this.CreateDbCommand(commandText, CommandType.Text);

  command.Connection = conn;
  conn.Open();
  resultat = command.ExecuteNonQuery();
 }
 finally
 {
  if (conn.State != System.Data.ConnectionState.Closed) conn.Close();
 }
 
 // Retour du rsultat;
 return resultat;

}

/// <summary>
/// Excute un ordre T-SQL et renvoit une valeur
/// </summary>
/// <param name="CommandText">Requte  xcuter</param>
/// <returns>Valeur de retour</returns>
public Object ExecuteScalar(String commandText)
 {
  Object resultat = null;
  DbConnection conn = null;
  try
   {
    conn = this.GetConnection();
    DbCommand command = this.CreateDbCommand(commandText, CommandType.Text);

    conn.Open();
    resultat = command.ExecuteScalar();

   }
   finally
   {
    if (conn.State != System.Data.ConnectionState.Closed) conn.Close();
   }

   // Retour du rsultat;
   return resultat;

}
DbDataReader
Nous allons voir maintenant comment renvoyer facilement un DbDataReader générique. Pour ceux d'entre vous qui l'ont déjà expérimenté en Ado.Net 1.1 (pour renvoyer par exemple un SqlDataReader) vous aller voir que le code diffère peu finalement :
public DbDataReader GetReader(String commandText)
{
 DbDataReader dataReader = null;
 DbConnection conn = null;
 try
 {
  conn = this.GetConnection();
  DbCommand command = this.CreateDbCommand(commandText, CommandType.Text);

  conn.Open();
  dataReader = command.ExecuteReader(System.Data.CommandBehavior.CloseConnection);

 }
 catch
 {
  if (conn.State != System.Data.ConnectionState.Closed) conn.Close();
 }

 // Retour du dataReader;
 return dataReader;

}
Notez la clause System.Data.CommandBehavior.CloseConnection qui fermera notre connexion une fois que nous aurons fermé le DbDataReader.
DataSet
Petite nouveauté au niveau de la récupération d'un DataSet.
Avec ADO.net 1.1 nous étions obligé de passer par un Adapter.
Machinerie lourde mais certe indispensable si l'on veut manipuler noter DataSet pour mettre à jour supprimer, synchroniser, faire un Delta sur des données etc ...
Dans notre cas, ce qui nous intéresse, c'est de récupérer rapidemment les données, sans se soucier du reste. Fast and ... Fast !

Nous allons donc utiliser une nouvelle méthode du DataSet 2.0 : .Load()
Cette méthode permet de charger des données en passant un IDataReader en paramètres.
L'objet de cet article n'est pas l'explication de manière exhaustive de cette méthode (Je vous conseille la librairie MSDN qui l'explique très bien)
Sachez cependant que si vous utilisez plusieurs fois cette méthode sur un DataTable, les donnés sont fusionnées et non pas écrasées. Attention différents comportements sont levés suivant le paramètre LoadOption fournie à la métode .Load()
Pour le reste rien de particulier que vous n'ayez déjà vu :
public void GetDataSet(DataSet dataSet, String tableName, String commandText)
{

    DbConnection conn = null;
    DbDataReader dbreader = null;

    try
    {
    conn = this.GetConnection();

    DbCommand command = this.CreateDbCommand(commandText, conn, CommandType.Text);

    conn.Open();

    dbreader = command.ExecuteReader(CommandBehavior.CloseConnection);

    dataSet.Load(dbreader, LoadOption.PreserveChanges, new string[] { tableName });

    }
    finally
    {
    if (conn.State != System.Data.ConnectionState.Closed) conn.Close();
    }

}
Procédures stockées
Maintenant que nous avons implémenté la plupart des méthodes d'accès à une base de données générique, il nous faut pouvoir exécuter les procédures stockées de notre base, pour autant que celle-ci nous le permette.
La principale difficulté vient des paramètres de nos procédures stockées, qui peuvent varier en quantité, en nomenclature et en typage.
Il est impossible de prévoir ces paramètres dans une classe qui se veut générique, il nous faut donc Dériver les paramètres de celle ci.
Heureusement pour nous, il existe sur la plupart des bases de données une méthode pour récupérer ces dits paramètres.
Cette méthode est fournie par l'objet Command : DeriveParameters.
Par exemple, dériver les paramètres d'une procédure stockée venant d'une base de donnée de type Sql Serveur s'effectue de la manière suivante :
connexion.Open();
System.Data.SqlClient.SqlCommandBuilder.DeriveParameters(command);
connexion.Close();
Une fois DeriveParameters() exécuté, notre command possède alors une collection complète des paramètres d'entrée de notre procédure stockée.
Attention : Ne vous y trompez pas ; Il n'y a rien de miraculeux la dedans ! C'est un ordre sql qui est appelé ! Ce qui veut dire qu'à chaque appel d'une procédure stockée, non pas UN mais DEUX ordres sql sont exécutés sur la base de donnée...
Qui plus est, tous les providers de données ne fournissent pas un DeriveParameters. Nous allons donc tenter de passer outre et nous adapter à 99,9% des bases de données ;) Pour le 0,1 % restant, un peu de réflexion et de réflection (facile celle là :-)) peut nous sauver la vie.
private void DeriveParameters(DbCommand command, DbConnection connexion)
{

    if (command is System.Data.SqlClient.SqlCommand)
    {
        connexion.Open();
        System.Data.SqlClient.SqlCommandBuilder.DeriveParameters((System.Data.SqlClient.SqlCommand)command);
        connexion.Close();
    }
    else if (command is System.Data.Odbc.OdbcCommand)
    {
        connexion.Open();
        System.Data.Odbc.OdbcCommandBuilder.DeriveParameters((System.Data.Odbc.OdbcCommand)command);
        connexion.Close();
    }
    else if (command is System.Data.OleDb.OleDbCommand)
    {
        connexion.Open();
        System.Data.OleDb.OleDbCommandBuilder.DeriveParameters((System.Data.OleDb.OleDbCommand)command);
        connexion.Close();
    }
    else
    {
        // ---------------------------------------
        // Tentative de rflexion
        // ---------------------------------------
        try
        {
            DbCommandBuilder commandBuilder = DbProviderFactory.CreateCommandBuilder();

            // Rcupration du Type du commandBuilder cre par mon Provider
            Type commandBuilderType = commandBuilder.GetType();

            // Tentative de rcupration de la mthode DeriveParameters
            MethodInfo methodInfo = commandBuilderType.GetMethod("DeriveParameters");

            // Si la mthode n'existe pas, exeception leve
            if (methodInfo == null)
                throw (new Exception("DeriveParameters method is not suppored by the selected provider"));

            // Invocation de la mthode DeriveParameters
            methodInfo.Invoke(null, new object[1] { command });
        }
        catch (Exception ex) { throw ex; }
    }

}
Une fois nos paramètres récupérés, il ne reste plus qu'à affecter leurs valeurs.
J'ai par la suite surchargé la plupart des méthodes qui exécutent un ordre, avec en paramètres non pas l'ordre Sql mais le nom de la procédure stockée suivi de la liste de paramètres attendus par celle ci.
Pour la performance, le problème de double exécution lors du DeriveParameters, il suffit de mettre en place une HashTable static qui gardera en mémoire les paramètres de la procédure stockée. Ainsi pour chaque Appel, la méthode DeriveParameters n'est éxécuté qu'une seule et unique fois.
DbParameterCollection parametresLocaux = null;

if (!cache.ContainsKey(command.CommandText))
{
    lock (cache)
    {
        if (!cache.ContainsKey(command.CommandText))
        {

            this.DeriveParameters(commandeLocale, connexionLocale);

            if (this.CacheEnabled)
            {
                cache.Add(command.CommandText, commandeLocale.Parameters);
            }
            else
            {
                parametresLocaux = commandeLocale.Parameters;
            }
        }
    }
}
Vous noterez l'utilisation du Pattern Double Check pour éviter les problèmes multi threads.
Je vous invite à étudier la méthode PrepareCommand de notre classe pour plus de détails.
 
» Démarrer une discussion
 
Discussion démarée par hugues le 06/10/2008 à 13:56, 1 commentaire(s).
Discussion démarée par SonOfGod le 16/06/2009 à 11:57, 1 commentaire(s).