Protéger vos requêtes SQL à l’aide des requêtes paramétrées.

30. September 2010 by Mustapha.AMALLAHI

Introduction

Lorsque vous travaillez avec des données, vous aurez souvent besoin de filtrer les résultats en fonction de certains critères. Typiquement, cela se fait en acceptant la saisie d'un utilisateur et en utilisant cette entrée pour former une requête SQL. Par exemple, une personne pourrai avoir de voir toutes les commandes entre des dates spécifiques.

Une autre requête pourrait être simplement de récupérer des champs suivant l’ID d’un enregistrement.


Comme vous le savez, la requête SQL assignée à un objet SqlCommand est simplement une chaîne.

Donc, si vous souhaitez filtrer une requête, vous pouvez créer la chaîne dynamique, mais cela est risqué car la requête n’est pas protégée.

 

Voici un mauvais exemple de filtrage d'une requête.

Ne jamais créer une requête de cette façon! La variable d'entrée,  idTable, est généralement récupérée à partir d'un contrôle TextBox soit sur un formulaire Windows ou une page Web. Tout ce qui est placé dans le contrôle TextBox sera mis en idTable et ajouté à votre chaîne SQL. Cette situation peut inciter un pirate à remplacer cette chaîne par un code malveillant.


Au lieu de construire dynamiquement une chaîne, comme le montre le mauvais exemple ci-dessus, utilisez les requêtes paramétrées.

Tout ce qui est placé dans un paramètre sera traité et ne fait pas partie de l'instruction SQL, ce qui rend votre application beaucoup plus sûre.

L'utilisation de requêtes paramétrées est un processus en trois étapes:

   1. Construire la chaîne de commande SqlCommand avec des paramètres.
   2. Déclarez un objet SqlParameter en lui attribuant une valeurs.
   3. Assignez l'objet SqlParameter à la propriété de l'objet SqlCommand.

Les sections suivantes vous guident étape par étape à travers ce processus.

 

La préparation d'un objet SqlCommand pour les paramètres

La première étape dans l'utilisation de paramètres dans les requêtes SQL est de construire une chaîne de commande contenant des espaces réservés de paramètres. Ces espaces réservés sont remplis avec des valeurs de paramètre réel quand la SqlCommand est exécuté.

La syntaxe correcte d'un paramètre est d'utiliser un préfixe symbole "@" sur le nom du paramètre comme indiqué ci-dessous:

 

Dans le constructeur de SqlCommand ci-dessus, le premier argument contient une déclaration de paramètre, @id. Cet exemple utilise un paramètre, mais vous pouvez avoir autant de paramètres que nécessaire pour personnaliser la requête. Chaque paramètre correspondant à un objet SqlParameter qui doit être affectés à cet objet SqlCommand.

 

Déclarer un objet SqlParameter

Chaque paramètre dans une instruction SQL doit être défini. Tel est l'objet du type SqlParameter. Votre code doit définir une instance de SqlParameter pour chaque paramètre de commande d'un objet SqlCommand de SQL. Le code suivant définit un paramètre pour le paramètre @id de la section précédente:

 

Notez que la propriété ParameterName de l'instance SqlParameter doit être écrite  exactement comme le paramètre qui est utilisé dans la chaîne de commande SQL SqlCommand. Vous devez également spécifier une valeur pour la commande. Lorsque l'objet SqlCommand s'exécute, le paramètre sera remplacé par cette valeur.

 

Associer un objet SqlParameter avec un objet SqlCommand



Pour chaque paramètre défini dans la chaîne de commande de l’objet SqlCommand, vous devez définir un SqlParameter.

Vous devez également ajouter à l'objet SqlCommand tous les SqlParameter en attribuant l'instance SqlParameter à la propriété Parameters de l'objet SqlCommand.

 Le code suivant montre comment faire:


L'instance SqlParameter est l'argument de la méthode Add de la propriété Parameters de l'objet SqlCommand ci-dessus.

Vous devez ajouter un unique SqlParameter pour chaque paramètre défini dans la chaîne de l'objet SqlCommand de commande SQL.

 

On met tout ensemble.


Résumé :


Vous devez utiliser des paramètres pour filtrer les requêtes d'une manière sûre, un très bon moyen pour éviter les injections SQL.

Le processus de d’utilisation du paramètre comporte trois étapes:

-définir le paramètre dans la chaîne de commande SqlCommand.

-déclarer l'objet SqlParameter avec des propriétés applicables.

-Affecter l'objet SqlParameter à l'objet SqlCommand.

Lorsque le SqlCommand est exécuté, les paramètres seront remplacés par les valeurs spécifiées par l'objet SqlParameter.

 

Voilà vous savez tout sur les requêtes paramétrés ! Écouter

Lire phonétiquement

 

ADO.NET, C# , , , ,

WPF - Animations sous Expression Blend

10. September 2010 by Lo�c.GRISPINO

Introduction


Bonjour,

Tout au long de cet article, vous allez apprendre les animations en WPF. Pour cela, je me suis basé sur le précédent article :


Ce qu’il va nous falloir :

-         Le projet de la fenêtre de notification : Window_Notif.zip (83,36 kb)

-         Expression Blend ( dans ce tutorial j’utilise Expression Blend 4, mais c’est exactement le même principe avec la version 3. )

-     Visual Studio


Une simple animation


Donc, nous nous sommes arrêté à une simple fenêtre qui apparaît et disparait au bout de quelques secondes.


Ce serait quand même beaucoup mieux si tout ceci était animé !


Pour commencer on ouvre le projet dans Expression Blend. Donc comme d’habitude :

File -> Open Project/Solution

Et on lui donne le .sln, exactement comme dans Visual Studio.


Dans l’onglet « Projects », il suffit de double cliquer sur la fenêtre à éditer.



A cet endroit, il faut bien penser à sélectionner [Window] avant de cliquer sur le signe + pour ajouter un storyboard.


Sinon vous créeriez une animation uniquement sur le calque choisi.


Donnez un nom à ce storyboard ( « Pop »  par exemple )


A ce moment le cadre deviens rouge pour montrer que ça enregistre les actions, donc évitez de bouger tout n’importe sinon cela risque de voir. Si éventuellement vous souhaitez deplacer un contrôle ( parce qu’il est mal placé par exemple ). Pensez à cliquer sur la petite boule rouge en haut à gauche pour suspendre l’enregistrement.


Le principe est assez simple, à gauche se trouve une timeline. Le curseur est placé à l’instant 0. Comme on veux faire une animation pour l’apparition, il faut qu’au début de l’animation, la fenêtre soit absente, puis arrive en glissant tout en apparaissant.


Ainsi, curseur à 0 ms, on vérifie que c’est toujours [window] qui est sélectionné, on met l’opacité à 0 du Border et on reste appuyé sur la flèche vers le bas jusqu'à ce que la fenêtre soit hors du cadre noir.


Note : Le fait de rester appuyé sur la touche fléchée vers le bas pose des soucis lors des « ctrl+z » en cas d’erreur mais est très précis et évite de décaler la fenêtre.



Note : Le cadre noir est en quelque sorte notre « champ de vision » tout ce qui est hors de celui ne sera pas visible.


Il ne reste plus qu’à se placer à 1s et de remettre la fenêtre en place et l’opacité du Border à 90%.

 

La première animation est finie !


 

Voici la vidéo des actions à faire :



Pensez au plein écran pour une meilleure visiblité !


Ainsi de suite


Nous allons maintenant faire l’animation pour la disparition. Cette étape va être très rapide car il suffit de :

-         Dupliquer le storyboard Pop

-         Renommer Pop_Copy en Depop

-         Inverser


Pour cela un clic droit sur le nom du storyboard nous affiche le menu contextuel.


Voici la manip :




Pensez au plein écran pour une meilleure visiblité !

Lier à l’évènement

Voila, on a nos 2 animations, il faut maintenant leur dire de se lancer lors de l’ouverture et la fermeture de la fenêtre.


Pour cela, il faut aller dans l’onglet « Triggers » ( ou déclencheurs ) en français. Et d’ajouter les évènements.


Par défaut, window.Loaded s’est ajouté avec l’animation Pop dedans. Vérifiez qu’il n’y a bien que cette animation avant de passer à la suite, parce que parfois ça ajoute n’importe laquelle.



Il ne nous reste plus qu’a ajouter l’animation Depop à la fermeture. Il faut donc cliquer sur « +Event », sélectionner le nouveau déclencheur et le remplir ainsi :



Animation à la fermeture


Le souci qu’on a maintenant, c’est que l’animation se fait bien lors de l’ouverture de la fenêtre mais celle de la fermeture n’a pas le temps d’avoir lieu !


C’est à ce moment que ça se complique un peu. Sauvegardez le projet, fermez Expression Blend puis ouvrez le projet dans Visual Studio.


Dans le code XAML ( design ) :


Dans le paragraphe <Window> , ajoutez l’évènement “OnClosing” et donnez lui un nom :

Closing="Window_Closing"



Trouvez le paragraphe :

<Storyboard x:Key="Depop">

[…]

</Storyboard>

 

Il faut ajouter l’évènement Completed pour pouvoir l’utiliser.


<Storyboard x:Key="Depop" Completed="closeStoryBoard_Completed">

 

Le code doit ressembler à ça :

 

 

 Dans le code C# :


Il faut ajouter la variable booléenne qui passera a TRUE quand l’animation sera finie.

private bool closeStoryBoardCompleted = false;

 

 

 


Et ajouter à la suite de notre classe les 2 méthodes qui permettront d’attendre la fin de l’animation avant de fermer la fenêtre.




Voici donc le rendu final :




Pensez au plein écran pour une meilleure visiblité !

Vous pouvez également télécharger l'archive du projet fini : Window_Notif_anim.zip (83,36 kb)

 

Voila, ce tutorial est fini , je vous remercie pour votre interêt et vous souhaite un bon codage.

ADO.NET, C#, Visual Studio 2010, WPF , , , , , , , , ,

WPF - Création d'une fenêtre de notification.

25. July 2010 by Lo�c.GRISPINO

Préambule

Bonjour,

Au cours de cet article, vous allez apprendre à réaliser une jolie petite fenêtre de notification qui apparaitra en bas à droite de votre écran, un peu comme celle de MSN lorsque quelqu'un se connecte.

Pour la rédaction de cet article, j'ai simplement ajouté un bouton dans ma fenêtre Window1 pour faire apparaitre notre notification.

Les utilités d'une telle fenêtre sont, par exemple, signaler une erreur à l'utilisateur ( champs de formulaire mal remplis, problème quelconque ...), afficher une confirmation à l'utilisateur (login réussi, opération effectuée etc ... ). Le but est d'afficher quelque chose sans que l'utilisateur ne soit géné et n'ai à cliquer dessus.

Voici une vidéo du rendu final :

 

Ajout d'une fenêtre

 

Pour commencer, on va créer une nouvelle fenêtre :

Clic droit sur votre projet, puis Add et New Item

 

Puis choississez "Window (WPF)


Ainsi pour faire apparaitre cette nouvelle fenêtre, il vous suffira de mettre cette ligne :

new Window_Notif().Show();

Note :  ShowDialog() nous ferais apparaître une fenêtre modale

Au clic d'un bouton, par exemple, ou lors d'un evènement précis.

 

Mise en forme de la fenêtre


Voici le code XAML de la fenêtre lors de sa création. Donc un simple carré blanc, avec un GRID à l'interieur pour pouvoir placer ses contrôles.

Nous allons donc réaliser un design rapide. J'ai tout d'abord redimensionné la fenêtre avec pour dimensions :

 

Height="160" et Width="300"

 

Bords arrondis et couleur

Pour commencer, nous allons réaliser des bords arrondis. Pour cela, il faut insérer une bordure à l'intérieur de la fenêtre (toujours) et autour du grid, car celui ci n'accepte pas les attribut que nous allons assigner à la bordure.

 

 

Et ensuite, il faut donc faire quelques ajouts :

Concernant la fênetre en elle même :

On permet la transparence ( et par ce fait, il n'y aura pas de bordure ) , on enlève la barre en haut ( agrandir, fermer et rétrécir) et nous choississons un fond transparent ( qui est blanc par défaut)

 AllowsTransparency="True" WindowStyle="None" Background="Transparent"


Concernant la bordure :

 

On veux des angles arrondis ( le coefficient arrondi plus ou moins, une valeur de type 10,10,0,0 aurait juste arrondi les bords supérieurs ), on veux une bordure de 1 pixel, de couleur noire. Puis une opacité de 0.9.

 CornerRadius="10" BorderThickness="1" BorderBrush="Black" Opacity="0.9"

 

Pour le fond, j'ai choisi un dégradé sur ton bleus, je l'ai réalisé sous Expression Blend, mais ce n'est pas compliqué à comprendre le principe, juste un peu plus fastidieux.

        <Border.Background>
            <LinearGradientBrush MappingMode="RelativeToBoundingBox" EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#FFD1D5FF"/>
                <GradientStop Color="#FF919BE4" Offset="1"/>
                <GradientStop Color="#FDF9F9F9" Offset="0.368"/>
            </LinearGradientBrush>
        </Border.Background>

 

Ce qui nous donne ceci en codage et en apparence.

 

 

C'est un design assez simple et peu recherché mais le principal y est.

 

J'ai également un TextBlock pour voir à quoi cela ressembler :

L'utilisation d'un TextBlock au lieu d'un label est due au fait que l'argument TextWrapping n'existe pas pour ce dernier.

<TextBlock Margin="2,43,6,14" Name="textBlock1" TextWrapping="Wrap" TextAlignment="Center" Text="Ceci est une notification."/>

TextWrapping pour le retour à la ligne automatique, et TextAlignment pour le centrer.

 

Apparition et fermeture

 

A l'heure actuelle, notre notification s'ouvre un peu n'importe où et ne disparais pas, nous allons régler cela simplement en modifiant le code behind de celle ci.

 

Emplacement :

On veux la fenêtre en bas à droite de l'écran :

this.Top = SystemParameters.PrimaryScreenHeight - this.Height;
this.Left = SystemParameters.PrimaryScreenWidth - this.Width - 10;

J'ai légèrement décalé vers la gauche pour une meilleure lisibilité.

On la veux toujours au premier plan :

this.Topmost = true;

 

Texte de notification :

Il nous suffit d'ajouter un argument dans le constructeur puis d'assigner la valeur au text du TextBlock

        public Window_Notif(string message)
        {

...

textBlock1.Text = message;

      }

 

Ce qui nous donne cela :

 

Fermeture


Etant donné que la fenêtre n'a plus de bouton "Fermer" il faut donc régler ce détail. Pour ne pas gêner l'utilisateur lors de l'apparition du cadre, nous allons faire en sorte quelle se ferme toute seule au bout de quelques secondes, qu'elle reste ouverte quand on met le curseur dessus, et qu'elle se ferme quand on clic dessus.


Mise en place du timer

Le timer en question sera de type "DispatcherTimer"

Nous l'initialisons en dehors du constructeur :

DispatcherTimer messageTimer = new DispatcherTimer();

puis, à la fin de notre constructeur, nous choisissons le temps avant l'action et nous lançons le timer. J'ai choisi 3 secondes pour pouvoir lire le message.

 

messageTimer.Tick += new EventHandler(messageTimer_Tick);
messageTimer.Interval = new TimeSpan(0, 0, 0, 0, 3000);
messageTimer.Start();

 

Il nous reste donc à determiner l'action à faire lors du "tick" du timer. Donc, en l'occurence, toutes les 3 secondes.

 

void messageTimer_Tick(object sender, EventArgs e)
{
//Il faut stopper le timer sinon il continue toujours.
messageTimer.Stop();
this.Close();
}

Voila, le timer se ferme tout seul au bout de 3 secondes.

Votre code devrais donc ressembler à cela :

 

 

Actions avec la souris

 

Il nous faut alors récuperer les évènements de la souris pour fermer ou, au contraire laisser la fenêtre ouverte.

 

Dans la partie design :

On ajoute des paramètres à la suite des caractériques de notre fenêtre ( dans notre <Window > )

 

MouseEnter="MouseIn" MouseLeave="MouseOut" MouseLeftButtonDown="WindowClicked"

 

Retour dans le code behind :

On code les méthodes que nous avons nommées juste avant.

 

private void MouseIn(object sender, MouseEventArgs e)
{
    messageTimer.Stop();
}

private void MouseOut(object sender, MouseEventArgs e)
{
    messageTimer.Start();
}

private void WindowClicked(object sender, MouseButtonEventArgs e)
{
    this.Close();

}

 

Utilisation

Maintenant que tout est paramétré, un simple appel du constructeur avec .Show() fera apparaître la fenêtre.

new Window_Notif("Voici un exemple de notification ! ").Show();

 

Aller plus loin

On peux bien sûr beaucoup améliorer cette fenêtre en ajoutant des animations à l'ouverture ET à la fermeture ( plus compliqué ) faites à l'aide de Expression Blend. Et également modifier le constructeur pour prendre en argument un booléan qui determinera si le message est une erreur ou un simple message.

Voici une petite vidéo pour vous montrer ce qu'on peux faire.

 

Vous pouvez télécharger le projet fini : Window_Notif.zip (83,36 kb)

Je vous remercie pour votre interêt et vous souhaite un bon codage !


ADO.NET, C#, Visual Studio 2010, WPF , , , , , , , , , , , , ,

Gérer une base de donnée MySQL avec ASP.net et C#, comparaison avec PHP

18. April 2010 by Didier.BOFF

Bases de données avec ASP.net / C# et parallèle avec PHP/MySQL

Plan :

       I.            Introduction

     II.            Connexion à la base

A.    Les fournisseurs de données

B.    La ConnectionString

C.    Exemple comparatif ASP.net/PHP

  III.            Effectuer un Select

A.    L’objet Command

B.    L’objet DataReader

C.    Exemple comparatif ASP.net/PHP

 IV.            Update de la base

A.    La méthode ExecuteNonquery()

B.    Exemple comparatif ASP.net/PHP

    V.            Conclusion

Annexe : Installer un driver ODBC pour MySQL

 

I.       I. Introduction

Cet article s’adresse aux développeurs débutants ayant une première expérience en XHTML, SQL et ASP.net / C#, particulièrement à ceux ayant déjà manipulé des bases de données en PHP/MySQL. Un parallèle sera fait avec ce langage, en rouge à la fin de chaque section.

Au cours d’une première partie, nous verrons une méthode permettant de se connecter à différents types de bases de données, notamment SQL Server, Access et MySQL. Les parties suivantes expliqueront comment opérer sur une base avec des requêtes SQL.

 

II. Connexion à la base

A. Les fournisseurs de données

ASP.net peut accéder aux différents types de bases de données grâce à un système de fournisseurs de données (data provider).

Le choix du provider dépend de nos besoins et nous l’expliciterons en utilisant un namespace approprié.

Voici les quatre data provider à connaitre, leur utilité et leur namespace :

Fournisseur de données 

Domaine d’application

Namespace

SQL Server Provider

MS SQL 7.0 et supérieur

System.Data.SqlClient

OLE DB Provider*

Ex : MS Access

System.Data.OleDb

OBDC Provider*

Ex : MySQL

System.Data.Odbc

Oracle Provider

Oracle Database 8i et supérieur

System.Data.OracleClient

*Notons qu’OleDB (Object Linking and Embedding, Database) et ODBC (Open Database Connectivity) permettent  d’établir une connexion avec (potentiellement) n’importe quelle base de données. Pour cela, ils sont employés en conjonction avec un driver selon le type de base utilisée. En annexe est présenté le driver ODBC MySQL pour Windows.

Les data providers ont chacun une façon différente de gérer l’accès à la base de donnée, cependant la méthode de connexion se ressemble pour tout les fournisseurs de données : on utilise la classe « Connexion ». Cette classe se présente différemment selon le provider utilisé (seul son nom change).

Ci-dessous est présentée la syntaxe différentes bases :

Provider

SQL Server

OleDB

OBDC

Oracle

Connexion

SqlConnection

OleDbConnection

ObdcConnection

OracleConnection

Exemple  d’instanciation de Connection avec SQL Server :

            SqlConnection myConnection = new SqlConnection() ;

B. ConnexionString

Une fois « Connection » instanciée, nous avons besoin de l’initialiser avec des paramètres tel que le mot de passe et le nom de la base. Pour cela on utilise le « ConnectionString » : un champ string de l’objet « Connection » ou l’on renseigne les paramètres de connection.

Exemple avec SQL Server :

myConnection.ConnectionString = "Data Source=localhost ; Initial Catalog=Pubs ; User ID=Dblast ; Password=Supinf0;” ;

Exemple avec ODBC et MySQL :

myConnection.ConnectionString = "DRIVER={MySQL ODBC 5.1 Driver}; SERVER=127.0.0.1;DATABASE=maBase;UID=Dblast;PWD=Supinf0;" ;

 

Une autre possibilité consiste à passer un string au constructeur overloadé de l’objet connection.

Exemple avec OleDB et Access:

string ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\myFolder\myAccess2007file.accdb;Persist Security Info=False; " ;

OleDbConnection myConnection = new OleDbConnection(ConnectionString) ;     

 

Enfin, une troisième et dernière possibilité permet de stocker différentes chaines de connection dans le fichier web.config, de façon à pouvoir les utiliser par un nom (relativement) plus explicte. Il suffit d’ajouter un fils <connectionStrings> à <configuration>, dans lequel on renseigne l’élément <add> avec les champs « name » et « connectionString » :

<connectionStrings>

<add name="Mysql" connectionString="DRIVER={MySQL ODBC 5.1 Driver};SERVER=127.0.0.1;DATABASE=maBase;UID=Dblast;PWD=FTW;"/>

</connectionStrings>

( note : Attention, l’élément <configSections> ne peut être précédé par <connectionStrings> ni aucun autre ! C’est l’élément racine apres <configuration> )

L’élément ainsi ajouté peut être affecté à la connection string en passant par l’objet « WebConfigurationManager » :

string ConnectionString = WebConfigurationManager.ConnectionStrings["Mysql"].ConnectionString;

Cet utilisation du fichier web.config nécessite le namespace “System.Web.Configuration“.

Pour en savoir plus sur les paramètres de ConnectionString , je vous conseille fortement d’aller sur le site : http://www.connectionstrings.com/ la référence sur le sujet. Vous pourrez y trouver tout les détails nécessaires à leur utilisation. Vous constaterez au passage la grande variété de bases de données existantes et les data providers supplémentaires (par exemple DB2 .NET pour les bases IBM).

Une fois l’objet connection instancié avec son ConnectionString, il ne reste plus qu’à exécuter sa méthode Open() tel que : myConnection.Open()

De même pour terminer la connexion, on utilise la méthode Close().

C. Exemple de synthèse et parallèle avec PHP/MySQL :

Dans cet exemple, nous nous connectons en local à une base  MySQL appelée « Supinfo » avec l’utilisateur « student » et le mot de passe « ftw ». Le driver ODBC MySQL 5.1 (disponible en annexe avec sa procédure d’installation) est alors utilisé avec ASP.net.

ASP.net

1. string ConnectionString = “DRIVER={MySQL ODBC 5.1 Driver}; SERVER=localhost;DATABASE=Supinfo;UID=student;PWD=ftw;”

2. OdbcConnection MyConnection = new OdbcConnection(ConenctionString);

3. myConnection.Open() ;

4. // Data retrieving

5. myConnection.Close() ;

PHP

1. $ConnectionString =   mysql_connect('localhost', 'student', 'ftw');

2.  mysql_select_db('Supinfo',$ConnectionString);

3.  // Data retrieving

4.  mysql_close($ConnectionString);

 

III. Effectuer un Select

Nous venons de voir l’existence d’un objet connection similaire pour chaque data provider, de la même façon il existe des objets Command et DataReader, utilisables pour récupérer des données dans la base.

Comme vous l’aurez sans doute remarqué, il faut simplement ajouter le nom du data provider utilisé pour se servir de la classe correspondante, par exemple :

OdbcCommand / SqlCommand / OleDbDataReader / OracleDataReader

Command servira à enregistrer nos requêtes alors que DataReader enregistrera les données.

A.L’objet Command

L’objet Command s’utilise avec deux attributs : Connection et CommandText. Très simplement, Connection est de type Connection et CommandText retient une requête SQL de type string. On peut affecter une valeur aux attributs dès l’instanciation grâce au constructeur overloadé de Command : Command(CommandText, Connection) .

B. L’objet DataReader

Une fois notre objet Command créé, on ajoute dans le code l’objet DataReader qui n’a pas besoin de paramètre. En effet, cet objet se contente de retenir les données (à la manière d’une ressource retourné par mysql_query() en php).

Le plus simple pour lire les données sera d’exécuter la méthode ExecuteReader() de Command, qui pour génère un objet de type DataReader. On l’utilisera dans une boucle pour récupérer les entrées des champs d’une table, grâce à sa méthode read() qui retourne une par une chaque entrée dans un tableau d’association avec les champs (comme mysql_fetch_array() en php).

C. Exemple

Dans l’exemple suivant, nous effectuons un SELECT * sur une table ‘élèves’ d’une base Supinfo, selon les cinq étapes qui suivent :

1. On affecte la requête à la variable « commandText »

2. Connexion à la base Supinfo (vue en première partie)

3. On récupère la ressource SQL dans « myCommand »

4. On boucle dans le « reader » pour afficher les entrées de la table

5. On ferme la connexion

Exemple en ASP.net :

1. string commandText = “SELECT * FROM élèves” ;

2. string ConnectionString = “DRIVER={MySQL ODBC 3.51 Driver}; SERVER=localhost;DATABASE=Supinfo;UID=student;PWD=ftw;”

2. OdbcConnection myConnection = new OdbcConnection(ConenctionString) ;

2. myConnection.Open() ;

3. OdbcCommand myCommand = new OdbcCommand(commandText, myConnection) ;

4. OdbcDataReader reader ;

4. reader = myCommand.ExecuteReader();

4. while( reader.read() ) { Response.Write(“<p>” + reader[ ID“]  + “</p>”) }

5. myConnection.Close() ;


Equivalent en PHP :

1. $commandText = ‘SELECT * FROM élèves’ ;

2. $ConnectionString = mysql_connect('localhost', 'student', 'ftw');

2. mysql_select_db('Supinfo', $ConnectionString);

3. $myCommand = mysql_query($commandText) ; 

4. while( $reader = mysql_fetch_array($myCommand) ) { echo ‘<p>’ . $reader[‘ID’] . ‘</p>’; }

5.  mysql_close($ConnectionString);

 

IV. Update de la base

Il nous reste à voir comment insérer des données dans la base, ce qui ne sera pas bien long.

A. La méthode ExecuteNonquery()

Pour ce faire, nous aurons uniquement besoin de l’objet Command vue précédemment. En ajoutant une commande SQL d’update ou d’insertion à son attribut CommandText, il suffira d’exécuter la méthode ExecuteNonQuery() qui retourne un entier : utile pour savoir combien d’entrées ont étés mise à jour / insérés.

B.Comparatif ASP.net / PHP 

Dans ce dernier exemple nous insérons simplement trois entrées ‘19,5’, ‘15’, ’17,5’ dans le champ note d’une table « TpASP » et nous retournons le nombre d’entrées affectées.

Exemple ASP.net :

1. string commandText = “INSERT INTO TpASP(note) VALUES (’19,5’, ‘15’, ’17,5’)” ;

2. string ConnectionString = “DRIVER={MySQL ODBC 3.51 Driver}; SERVER=localhost;DATABASE=Supinfo;UID=student;PWD=ftw;”

2. OdbcConnection myConnection = new OdbcConnection(ConenctionString) ;

2. myConnection.Open() ;

3. OdbcCommand myCommand = new OdbcCommand(commandText, myConnection) ;

4. int affectedRows ; // facultative, pour afficher le nombre d’insertion

4. affectedRows = myCommand.ExecuteNonQuery() ; 

4. Response.Write(<p> + affectedRows.ToString() + “</p>“) ;

5. myConnection.Close() ;

équivalent PHP :

1. $commandText = ‘INSERT INTO TpASP(note) VALUES (’19,5’, ‘15’, ’17,5’)’;

2. $ConnectionString = mysql_connect('localhost', 'student', 'ftw');

2. mysql_select_db('Supinfo', $ConnectionString);

3. $myCommand = mysql_query($commandText) ; 

4. echo mysql_affected_rows();

5.  mysql_close($ConnectionString);

 

V.Conclusion

Ayant moi même développé en PHP avant d’apprendre ASP.net, les exemples des parties 2 et 3 s’appuient évidement sur MySQL. Cependant il faut garder à l’esprit que l’utilisation d’un driver (ODBC ou OleDB, au choix) ralenti la communication entre le serveur et la base. Il est donc plus judicieux d’utiliser ASP.net avec une base de donnée SQL Oracle ou Microsoft, pour ceux qui en ont la possibilité. En effet, les data provider correspondants permettent des performances plus élevés car ils accèdent directement à la base, sans passer par une couche supplémentaire.

Il faut également savoir que les classes exploitées dans ce document font parties de l’ensemble ADO.net (ActiveX Data Objects), qui fournit des méthodes d’exploitation de bases de données, mais il existe une nouvelle solution : LINQ. Sans rentrer dans les détails, LINQ permet d’envoyer des requêtes qui remplacent SQL, sns passer par un « Data Adapter ». C’est une technologie prometteuse et en plein essor.


Sources et liens :

Data Set et Data Adapter :

http://www.beansoftware.com/ASP.NET-Tutorials/DataSet-DataAdapter.aspx

Fournisseurs de données .NET Framework (ADO.NET)

http://msdn.microsoft.com/fr-fr/library/a6cd7c08.aspx

Connexion à MySQL avec C# et ODBC :

http://www.java2s.com/Code/ASP/ADO.net-Database/MySQLODBCconnectionC.htm

http://primaryobjects.com/CMS/Article68.aspx

http://dev.mysql.com/tech-resources/articles/dotnet/index.html

Connexion à Access et MSSQL :

http://www.aspfr.com/tutoriaux/ASP-BASE-DONNEES-BASES-DONNEES-ACCESS-SQL_9.aspx

LINQ avec C# et MySQL :

http://www.programmez.com/tutoriels.php?tutoriel=102&titre=Utilisation-de-LINQ-avec-la-base-MySQL

Livre : Beginning ASP.NET 3.5 in C# 2008: From Novice to Professional, Second Edition

http://apress.com/book/view/1590598911

// N’oubliez pas d’aller voir http://www.connectionstrings.com/ pour connaitre les paramètres de ConnectionString dont vous avez besoin.

 

Annexe : Installer un driver ODBC pour MySQL

 

Premièrement, nous avons besoin d’installer la dernière version du driver ODBC MySQL (5.1.6 à l’heure où j’écris ces lignes) disponible sur le site de MySQL :

http://dev.mysql.com/downloads/connector/odbc/5.1.html

Il s’agit du fichier « mysql-connector-odbc-5.1.6-win32.msi » pour windows, une rapide recherche sur google vous évitera l’inscription sur le dit site.

Une fois installé, ouvrez le panneau de configuration (outils d’administration pour Seven) puis « Sources de données (ODBC) », cliquez alors sur "ajouter".

Apres la sélection du driver, vous devrez le configurer avec un nom, une description et des identifiants.

Voilà, il ne vous reste plus qu’à utiliser une connexion string appropriée, par exemple :

connectionString="DRIVER={MySQL ODBC 5.1 Driver}; SERVER=127.0.0.1;DATABASE=maTable;UID=root;PWD=;";

Le driver est parfaitement utilisable avec PhpMyAdmin de WampServer. Comme ca, les dev PHP ne sont pas dépaysés !

ADO.NET, C#

Introduction à ADO.NET en C#

4. April 2010 by Aymeric.Lagier

ADO.NET est un ensemble de composants présents de base dans le framework .NET permettant l'accès et la gestion de données situées sur une base de données relationnelle (SQL Server, Oracle, etc...) ou non. ADO.NET est une évolution de ADO (ActiveX Data Objects). Les classes ADO.NET peuvent être divisées en 2 parties. Les classes permettant de se connecter à la source de données et les classes utilisées pour gérer les données.

La connexion à la source de données

Pour se connecter à une base de données via ADO.NET un Data Provider (fournisseur de données) correspondant à la base de données utilisée. Ainsi le Data Provider de SQL Server est optimisé pour fonctionner avec les bases de données SQL Server, idem pour le Data Provider Oracle. Tous les Data Providers sont dérivés d'une seule et même classe, ils implémentent les mêmes méthodes, propriétés, et fonctionnent donc de la même manière. Dans certains cas, les Data Providers peuvent implémenter des fonctionnalités spécifiques à la base de données utilisée (exemple des requêtes XML pour SQL Server). Les 4 fournisseurs de données inclus dans le framework .NET :

  • SQL Server Provider : pour SQL Server 7.0 et plus).
  • OLE DB provider : pour toutes les bases de données disposant d'un pilote OLE DB (Object Linking and Embedding).

Note:

OLE DB est une interface basée sur COM (Component Object Model) utilisée pendant plusieurs années avec ADO (ActiveX Data Objects), la plupart des bases de données ont donc un pilote OLE DB (MySQL, Access, Oracle SQL Server, etc..). Si la source de données ne propose pas de pilote OLE DB, vous pouvez le créer vous-même. Comme dit précédemment, OLE DB propose des pilotes pour SQL Server et Oracle qui ont déjà des Data Providers dédiés. Il est donc tout à fait possible d'utiliser OLE DB à la place du SQL Server Provider. La seconde méthode (SQL Server Provider) est préférable car elle offrira de meilleures performances.
  • Oracle provider : pour Oracle (version 8i et plus).
  • ODBC provider : pour toutes les sources de données qui ont un pliote ODBC (Open DataBase Connectivity).

Note :

ODBC est une suite de pilotes permettant la communication avec la pulpart des SGBD (Système de Gestion de Base de Données) du marché comme MySQL, Oracle, DB2, PostgreSQL, etc...

Côté code, les classes des Data Providers se trouvent dans les namespaces suivants :

  • System.Data.OleDb
  • System.Data.SqlClient
  • System.Data.OracleClient
  • System.Data.Odbc
Il ne faut pas oublié de rajouter à ces namespaces, System.Data qui contient les classes essentielles au fonctionnement d'ADO.NET. Déclaration d'un Data Provider pour SQL Server :

Déclaration des namespaces

Création de la connexion On construit donc la connexion avec la ConnectionString qui indique le serveur de base de données (localhost\SQLEXPRESS), la base de données (Northwind) et la manière de s'authentifier sur le serveur (SSPI). Note :
L'exemple ci-dessus utilise le Data Provider de SQL Server. Pour les autres Data Providers, il suffit de remplacer SqlConnection par OracleConnecion, OleDbConnection ou OdbcConnection. Le même principe s'applique pour les commandes qui suivront dans cet article.

La gestion des données

L'un des avantages d'ADO.NET est sa généricité. Peu importe la source de données, le conteneur qui accueillera les données sera le même, le DataSet. Pour faire une analogie avec le C# de base, un DataSet est en quelque sorte un tableau (array), mais spécialement adapté pour accueillir des données venant d'une source de données relationnelle.

ADO.NET peut fonctionner de 2 manières diffèrentes. En mode connecté (Direct Data Access), le programme se connecte à la base de données et fait un certain nombre d'opérations (SELECT, INSERT, UPDATE, etc...), puis la connexion est fermée. Aucune donnée n'est donc stockée en mémoire sur le client. En mode déconnecté, le programme se connecte à la base de données durant un temps très bref pour rapatrier les données en mémoire. La connexion est ensuite interrompue. Une fois les opérations (INSERT, DELETE, etc...) réalisées sur le DataSet, le programme se connecte à nouveau à la base de données et sauvegarde les changements.

Quel mode choisir ?

L'utilisation du mode connecté est recommandé pour les sites web en ASP.NET puisque le cycle de vie d'un programme est totalement différent par rapport à une application de bureau. Dans une page web, le cycle de vie du programme n'est que de quelques secondes, les données n'ont donc pas besoin d'être stockées en mémoire. Concernant les applications lourdes, le mode déconnecté est utile lorsqu'il y a de nombreux traitements à faire sur les données avant de les modifier dans la base de données.

Le mode Connecté (Direct Data Access)

La commande SELECT

Détails du code :

Premièrement, il faut déclarer la connexion à la base de données, puis définir la requête à exécuter. On déclare un SqlDataReader qui contiendra nos données une fois la commande SQL exécutée. La suite du code est imbriqué dans un bloc try/catch/finally afin de gérer les éventuelles erreurs qui pourraient survenir. La connexion à la base de données est ouverte et la requête exécutée. La boucle while permet de parcourir tous les enregistrements afin de les afficher dans la console. A la fin de l'opération, on ferme le SqlDataReader ainsi que la connexion à la base de données. Le bloc finally permet de fermer la connexion à la base de données dans tous les cas (succès de la requête ou erreur).

Les commandes INSERT, UPDATE et DELETE

Le principe est le même, à l'exception du DataReader qui n'est plus utile et de la méthode permettant d'exécuter la requête : ExecuteReader() qui devient ExecuteNonQuery() puisque la requête ne renvoie que le nombre de lignes modifiées.

Le processus est identique pour le INSERT et le DELETE.

La protection des données dans les requêtes

Dans la requête ci-dessus, 'ALFKI' est inscrit en dur dans la requête, cela ne pause donc pas de problème. Si maintenant le CustomerID est une valeur entrée par l'utilisateur du programme. Si l'utilisateur entre la valeur 'ALFK'I, le programme affichera une erreur. Deuxième problème, les injections SQL. L'utilisateur du programme peut délibérément entrer des paramètres spéciaux qui détourneront la requête de son but initial.

Pour contrer ce type de problème, ADO.NET met en place les requêtes paramètrées : Requête normale : Requête paramétrée :

De cette manière un utilisateur peut entrer n'importe quelle chaine de caractères dans myVar, il n'y aura pas de problème d'injection SQL ni de chaine invalide. Ce processus fonctionne également pour le mode déconnecté expliqué ci-dessous.

Le mode Deconnecté (Direct Data Access)

La commande SELECT

Pour le mode déconnecté la processus est diffèrent. Le programme établit une connexion avec la base de données, récupère les données et referme immédiatement la connexion. Les opérations sur les données (affichage dans la console) ne se font qu'une fois la connexion fermée.

Détails du code :

Comme pour le mode connecté, la connexion à la base de données et la requête SQL sont déclarées. A la place du SqlDataReader, le SqlDataAdapter et le DataSet sont déclarés. Le SqlDataAdapter va permettre au programme de récupérer les données qui seront stockées dans le DataSet. Une fois les données enregistrées en mémoire, la connexion à la base de données est interrompue. Après la fermeture de la connexion, le programme peut traiter les données (affichage sur la console) contenues en mémoire.

Les commandes INSERT, UPDATE, DELETE

Pour les requêtes INSERT, UPDATE et DELETE, le processus est le même. C'est-à-dire se connecter à la base de données pendant un très court instant afin de charger la table qui nous intéresse en mémoire pour ensuite travailler dessus.

Détails du code : Comme dans la requête SELECT, la connexion et la requête SELECT sont définies. Petite nouveauté, la définition d'une commande UPDATE en plus de la commande SELECT ainsi que la déclaration de paramètres correspondants aux champs de la requête UPDATE. Après le stockage des données dans la mémoire, la modification des données peut s'opérer. Une fois les modifications réalisées, la méthode Update() de DataAdapter prend en paramètre la DataTable modifiée et envoie les données dans la base de données. Les exemples de cet article sont repris dans ce projet. Attention, pour fonctionner ce projet a besoin de la base de données Northwind disponible ici.

Conclusion

Cet article introduit l'ensemble de classes utilisées pour interagir avec une base de données en .NET en utilisant le mode connecté principalement pour l'ASP.NET et le mode déconnecté pour les applications de bureau exécutant un nombre important d'opérations sur les données. ADO.NET offrent d'autres fonctionnalités comme les relations entre les tables avec la classe DataRelation.

ADO.NET, C# , ,

Comment transformer un fichier texte CSV en base de données(2).

21. March 2010 by Alexandre.FROMAGE

Précédemment, nous avons vu comment utiliser ADO.NET pour lire un fichier texte au format CSV. Dans cet article, nous allons aller plus loin : il s’agit de créer une classe générique permettant de manipuler des bases de données au format texte facilement. Nous l’appellerons « TextFileDataAccess ».

Les colonnes contenues dans le fichier texte seront associées à une propriété publique d’une classe.

Ainsi chaque fichier texte CSV aura une classe correspondante.

En nous basant sur le fichier csv de l’article précédent, nous obtenons donc la classe « User » :

 

 

    class User 
    { 
        
public Int32 IdUser { getset; } 
        
public string FirstName { getset; } 
        
public string LastName { getset; } 
        
public string Mail { getset; } 
        
public DateTime BirthDate { getset; } 
    } 

 

Pour rappel, le fichier CSV :

IdUser,FirstName,LastName,Mail,BirthDate 
0,John,Dubois,jdubois@alexandrefromage.fr,06/11/1989


L’interface ITextFileDataAccess

La classe« TextFileDataAccess » implémentera l’interface « ITextFileDataAccess ».Cette interface expose les quatre méthodes du CRUD :

    interface ITextFileDataAccess<T> where T : class 
    { 

        
//CRUD 
        
void Insert(T objet);   //Create 
        
List<T> List();         //Read 
        
void Delete(T objet);   //Update 
        
void Update(T objet);   //Delete 
    } 

Ici le type « T » correspondrait par exemple à la classe « User ».

La classe « TextFileDataAccess »

Voici le code de cette classe, seules « Insert» et « List » ont été implémentées: 

 

 

    class TextFileDataAcces<T> : ITextFileDataAccess<T> where T:class 
    { 
        
public TextFileDataAcces(string connectionString, string fileName) 
        { 
            
this.connectionString = connectionString; 

            
this.fileName = fileName; 

            
//HeadersEnable renvoie true si les headers sont utilisés 
            
this.headersEnable = this.HeadersEnable(); 
        } 

        
private List<T> ParseFile() 
        { 
            
//On initialise la liste 
            
List<T> list = new List<T>(); 

            
//On initialise la connection 
            
OleDbConnection connection = new OleDbConnection() 
            { 
                ConnectionString = 
this.connectionString 
            }; 
             

            
OleDbCommand command = new OleDbCommand(); 
            command.CommandType = System.Data.
CommandType.Text; 
            command.CommandText = 
"SELECT * FROM " + this.fileName; 
            command.Connection = connection; 

            
//Utilisation de la réflexion pour récupérer les propriétés publiques de la classe T 
            
Type type = typeof(T); 
            
PropertyInfo[] properties = type.GetProperties(); 


            
OleDbDataReader reader; 
            
int increment; 

            
using (connection) 
            { 
                connection.Open(); 

                
using (reader = command.ExecuteReader()) 
                { 
                    
while (reader.Read()) 
                    { 
                            
//On utilise la réflexion pour créer une instance d'objet de type T 
                            
//La classe T doit contenir un constructeur PUBLIC SANS PARAMETRES 
                            T objet = (T)
typeof(T).GetConstructor(Type.EmptyTypes).Invoke(null); 

                            
// 
                            increment = 
0

                            
//On utilise la réflexion pour parcourir toutes les propriétés publiques de T 
                            
foreach (PropertyInfo property in type.GetProperties()) 
                            { 

                                
//on utilise la réflexion pour accéder à une propriété de l'objet de type T 

                                
//Si la première du fichier contient les headers 
                                
//Chaque nom de propriété publique de l'objet de type T doit correspondre à 
                                
//un header --> IndexOutOfRangeException 
                                
if(this.headersEnable) 
                                    property.SetValue(objet, reader[property.Name], 
null); 
                                
//Si il n'y-a pas de headers 
                                
//Le nombre de propriétés publiques de l'objet de type T doit correspondre au 
                                
//nombre de colonnes --> IndexOutOfRangeException 
                                
else 
                                    property.SetValue(objet, reader[increment], 
null); 

                                increment++; 
                            } 

                            list.Add(objet); 

                    } 
                } 
            } 

            
return list; 
        } 

        
/// <summary> 
        
/// Analyse la chaine de connexion pour savoir si les headers sont utilisés. 
        
/// </summary> 
        
/// <returns>True si la première ligne du fichier texte contient les headers, False sinon</returns> 
        
private bool HeadersEnable() 
        { 
            
//On utilise le caractère ';' pour découper la chaine de connexion. 
            
//';' sépare tous les paramètres 
            
string[] connParams = this.connectionString.ToLower().Split(';'); 

            
foreach (string param in connParams) 
            { 
                
//Si le paramètre courant est HDR, on regarde sa valeur. 
                
if(param.Contains("hdr")) 
                    
return param.Contains("yes")== truetrue : false
            } 

            
//La propriété HDR n'a pas été trouvée. 
            
throw new Exception("HDR property not set!"); 
        } 

        
/// <summary> 
        
/// Parse le fichier et renvoie la liste d'objets correspondant. 
        
/// </summary> 
        
/// <returns>Liste d'objets de type T</returns> 
        
public List<T> List() 
        { 
            
//On parse le fichier et on renvoie la liste crée. 
            
return this.ParseFile(); 

        } 

        
/// <summary> 
        
/// Insère un objet de type T dans le fichier 
        
/// </summary> 
        
/// <typeparam name="T"></typeparam> 
        
/// <param name="objet"></param> 
        
public void Insert(T objet) 
        { 
            
//On génère la requete a exécuter. 
            
//Utilisation de la réflexion pour récupérer les propriétés publiques de la classe T 
            
Type type = typeof(T); 
            
PropertyInfo[] properties = type.GetProperties(); 

            
//On initialise la connection 
            
OleDbConnection connection = new OleDbConnection() 
            { 
                ConnectionString = 
this.connectionString 
            }; 

            
//Requête sql 
            
StringBuilder request = new StringBuilder("INSERT INTO " + this.fileName); 
            
StringBuilder nameRequest = new StringBuilder("("); 
            
StringBuilder valuesRequest = new StringBuilder(" VALUES("); 

            
//Commande 
            
OleDbCommand command = new OleDbCommand(); 

                
foreach (PropertyInfo property in properties) 
                { 
                    
//On crée un paramètres en utilisant: 
                    
//le nom de la propriété 
                    
//La valeur de la propriété dans l'objet à insérer 
                    
OleDbParameter dbParam = new OleDbParameter("@" + property.Name, property.GetValue(objet, null)); 
                    command.Parameters.Add(dbParam); 

                    valuesRequest.Append(
"@" + property.Name + ","); 

                    
//Si on utilise les headers, on utilise le nom des propriétés publiques pour 
            
//déduire les noms de colonne 
                    
if (this.headersEnable) 
                        nameRequest.Append(property.Name + 
","); 
                } 

            
//Noms 
                
if (this.headersEnable) 
                { 
                    
//On supprime la dernière virgule 
                    nameRequest.Remove(nameRequest.Length - 
11); 
                    
//On ajoute une ) 
                    nameRequest.Append(
")"); 
                    
//On concatène les éléments de la requête 
                    request.Append(nameRequest); 
                } 

            
//Valeurs 
            
//On supprime la dernière virgule 
            valuesRequest.Remove(valuesRequest.Length - 
11); 
            
//On ajoute une ) 
            valuesRequest.Append(
")"); 
            
//On concatène les éléments de la requête 
            request.Append(valuesRequest); 


            
// 
            command.CommandType = System.Data.
CommandType.Text; 
            command.CommandText = request.ToString(); 
            command.Connection = connection; 

            
using (connection) 
            { 
                connection.Open(); 
                
//On exécute la requête 
                command.ExecuteNonQuery(); 
            } 
        } 

        
public void Delete(T objet) 
        { 
            
throw new NotImplementedException(); 
        } 

        
public void Update(T objet) 
        { 
            
throw new NotImplementedException(); 
        } 



        
private string connectionString; 
        
private string fileName; 
        
private bool headersEnable; 
    } 

Cette classe supporte les fichiers texte utilisant et n’utilisant pas les headers. La chaine de connexion est analysée dans le constructeur pour détecter si les headers sont utilisés.

Exemple d’utilisation

Pour utiliser « TextFileDataAccess » :

 

            //Avec headers 
            
string connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + 
                
"C:/Users/Alexandre/Desktop/;Extended Properties=\"text;HDR=Yes;FMT=Delimited\";"
            
string fileName = "testHeaders.csv"

            
//Sans headers 
            
//string connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + 
            
//    "C:/Users/Alexandre/Desktop/;Extended Properties=\"text;HDR=No;FMT=Delimited\";"; 
            
//string fileName = "testNoHeaders.csv"; 
             
            
TextFileDataAcces<User> daoUsers = new TextFileDataAcces<User>(connectionString, fileName); 


             
            
List<User> users = daoUsers.List(); 

            
User user = new User() 
            { 
                IdUser = 
51289
                FirstName = 
"Alexandre"
                LastName = 
"Fromage"
                Mail = 
"afromage@alexandrefromage.com"
                BirthDate = 
new DateTime(1989,11,06
            }; 

            daoUsers.Insert(user); 

 

Et voilà !

Vous pouvez maintenant utiliser très facilement des fichiers texte pour y stocker vos données.

Fichiers sources: ITextFileDataAccessTextFileDataAccess.

Article original.

ADO.NET, C# , , , ,

Comment transformer un fichier texte CSV en base de données(1)

21. March 2010 by Alexandre.FROMAGE

Récemment, j’ai du travailler sur une application qui devait traiter des données issues d’un fichier texte CSV (comma-separated values).

Pour des applications stockant peu de données et avec un modèle de données simple, l’utilisation d’une base de données relationnelle n’est souvent pas nécessaire.

Dans ce premier article, je vous expliquerai comment ADO.NET nous permet de communiquer avec un fichier texte, pour stocker des données.

Pour cela, il existe deux méthodes :

  • Lire le fichier texte à l’aide de StreamReader puis le parser.
  • Utiliser le fournisseur de données OLEDB avec ADO.NET.

Utiliser OLEDB simplifie grandement la manipulation des données stockées dans le fichier texte et nous permet d’être indépendants du format utilisé (SQL server, feuille excel, fichier texte…).

Dans cet article, nous allons voir comment utiliser un fichier texte CSV au travers d’OLEDB pour le stockage de nos données.

1. Le fichier CSV : Comma-Separated Values

En pratique, le séparateur peut être n’importe quel caractère.

Pour cet article, le fichier utilisé est le suivant :

IdUser,FirstName,LastName,Mail,BirthDate 0,John,Dubois,jdubois@alexandrefromage.fr,06/11/1989 1,Pierre,Duchamps,pduchamps@alexandrefromage.fr,07/12/1980 2,Paul,Dujardin,pdujardin@alexandrefromage.fr,23/01/2000 3,Henri,Dès,hdes@alexandrefromage.fr,15/02/1997 189,Alexandre,Dufort,adufort@alexandrefromage.fr,15/01/1960 12,Loïc,Dumaison,ldumaison@alexandrefromage.fr,19/06/1945


2. Lecture de données dans un fichier texte CSV avec ADO.NET

        //Connection string 
        
string connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + 
        
"C:/Users/Alexandre/Desktop/;Extended Properties=\"text;HDR=Yes;FMT=Delimited\";"

        
//Nom du fichier csv 
        
string fileName = "testHeaders.csv"

        
//On initialise la connection 
        OleDbConnection connection = 
new 
        OleDbConnection() 
        { 
            ConnectionString = connectionString 
        }; 

        
//On prépare la requête sql 
        OleDbCommand command = 
new 
        OleDbCommand() 
        { 
            CommandType = System.Data.CommandType.Text, 
            CommandText = 
"SELECT * FROM " + fileName, 
            Connection = connection 
        }; 

        
//Data reader 
        OleDbDataReader reader; 

        
//La connexion est automatiquement fermée à la fin du using 
        
using (connection) 
        { 
            
//Ouverture de la connexion 
            connection.Open(); 
            reader = command.ExecuteReader(); 

            
//On lit les enregistrements ligne par ligne 
            
while (reader.Read()) 
            { 
                
//Si on utilise la ligne de header 
                
Console.WriteLine(reader["IdUser"] + " " + reader["FirstName"]); 

                
//Sans header ? décommenter la ligne suivante 
                
//Console.WriteLine(reader[0] +  "  " + reader[1]); 

            } 
        } 

On obtient :

Ici, rien de compliqué. Le plus intéressant se concentre dans la chaine de connexion :

"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:/Users/Alexandre/Desktop/;Extended Properties=\"text;HDR=Yes;FMT=Delimited\";" 

Data Source: répertoire du fichier csv.

HDR indique si la première ligne du fichier CSV est le header(Yes) ou une ligne de données(No).

FMT indique comment les données sont séparées : « Delimited » si les colonnes sont séparées par une virgule ou « Fixed » si chaque colonne a une dimension. Dans ce dernier cas, il faut utiliser « Schema.ini » pour spécifier la taille des colonnes.

Schema.ini vous permet de spécifier :

-le nom du fichier texte

-le séparateur utilisé (format)

-les noms, tailles et longueur des champs des colonnes

Plus d’informations ici.

Dans le prochain article, nous créerons une classe générique, utilisant la réflexion, qui nous simplifiera la manipulation de fichiers CSV.

ADO.NET, C# , ,

Accès aux données générique avec Linq To SQL et C#

25. February 2010 by Pierre-Luc.ROUAYS

Accès aux données générique avec Linq To SQL et C#

Introduction :

Cet article permet de construire une couche d’accès aux données générique en utilisant LINQ To SQL. Ceci permettra par la suite d’effectuer les opérations basiques du CRUD. Nous créerons une classe nommée EntityCrud qui implémentera ces fonctionnalités.

Vous pourrez par la suite créer des Adapters pour chacune de vos classe dérivant ce cette classe.

 

Place à l’action !

Nous allons créer une simple solution contenant 2 projet : le programme et la couche d’accès aux données (DAL).

 

 

Ajouter un élément de type classe LINQ To SQL :

Puis ensuite ajouter les Tables à votre projet par le biais de l’explorateur de solution.

Code Projet MonProgramme.DAL

La classe EntityCrud contient tout le code nécessaire pour communiquer avec la base de donnée par le biais de LinqToSQL.

Voici la classe AdapterFactory :

 

 

using System;

using System.Collections;

using System.Collections.Specialized;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Linq;

using System.Data.Linq;

using System.Data.Linq.Mapping;

using System.Text;

using System.ComponentModel;  

The next section contains the namespace and class declarations. 

namespace MonProgramme.DAL

{

 

    /// <summary>

    /// Generic Interface to Database

    /// using LINQ to SQL

    /// </summary>

          public class EntityCrud

          {

 

 

Première fonction permettant de récuperer un objet par sa clé Primaire.

               /// <summary>

                   /// Select by Primary Key Value

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="id">The PK value to search for</param>

                   /// <returns>Single matching PK to id</returns>

                   public static T SelectByPK<T>(String id) where T : class

                   {

                             try

                             {

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                              // Récupère la table du type passé

                                                var table = context.GetTable<T>();

 

                              // Récupère le mapping de l’objet avec la Base

                                                MetaModel modelMap = table.Context.Mapping;

 

                              // Récupère les members de l’objet

                              ReadOnlyCollection<MetaDataMember> dataMembers

                              = modelMap.GetMetaType(typeof(T)).DataMembers;

 

                              // Cherche le nom du champ de la clé primaire

                              string pk = 

                              (dataMembers.Single<MetaDataMember>(m =>

                              m.IsPrimaryKey)).Name;

 

                              // Retourne l’objet unique qui correspond à cette clé primaire

                              // passée en argument

                                    return table.SingleOrDefault<T>(delegate(T t)

                                    {

                                    String memberId =

      t.GetType().GetProperty(pk).GetValue(t,

                                    null).ToString();

                                    return memberId.ToString() == id.ToString();

                                    }

      );

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

                   }

 

 

Seconde méthode permettant de récupérer toutes les objets de la base.

 

        /// <summary>

        /// Select All by Type

        /// </summary>

        /// <typeparam name="T"></typeparam>

        /// <returns>Matching table as typed list</returns>

        public static IList<T> SelectAll<T>() where T : class

        {

            try

            {

                using (NWindDataContext context = new NWindDataContext())

                {

                    // Récupère la table qui correspond au type

                    // et retourne tous les enregistrement sous forme de liste

                    var table = context.GetTable<T>().ToList<T>();

                    return table;

                }

            }

            catch (Exception)

            {

                throw;

            }

        }

 

Méthode d’insertion de donnée dans la base.

                   /// <summary>

                   /// Insert an Object into a Table

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="item"></param>

                   public static void Insert<T>(T item) where T : class

                   {

                             try

                             {

                // Récupère la table du type passé en argument

                // Insert l’enregistrement et soumet les changements à la Base

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                              // Récupère la table correspondante

                                                var table = context.GetTable<T>();

 

                              // Passe l’objet à la method InsertOnSubmit

                              // et Soumet les changements

                                                table.InsertOnSubmit(item);

                              context.SubmitChanges();

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

              }

 

Méthode de mise à jour de donnée.

 

       /// <summary>

                   /// Update an Existing Object

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="item">The object to update</param>

                   public static void Update<T>(T item) where T : class

                   {

                             try

                             {

                        // Crée une nouvelle instance de l’objet

                                      Object newObj = Activator.CreateInstance(typeof(T),    

                        new object[0]);

 

                                      PropertyDescriptorCollection originalProps =

                        TypeDescriptor.GetProperties(item);

 

                        // Donne la valeur de la donnée passée en argument à l’objet

                                      foreach (PropertyDescriptor currentProp in

                        originalProps)

                                      {

                                                if (currentProp.Attributes[typeof(

                              System.Data.Linq.Mapping.ColumnAttribute)] !=

                              null)

                                                {

                                                          object val = currentProp.GetValue(item);

                                                          currentProp.SetValue(newObj, val);

                                                }

                                      }

 

                        // Lie l’objet à un nouveau context de donnée

                        // et soumet les changement à la Base.

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                                                var table = context.GetTable<T>();

                                                table.Attach((T)newObj, true);

                                                context.SubmitChanges();

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

                   }

 

Code de suppression d’un objet.

               /// <summary>

                   /// Remove an object

                   /// </summary>

                   /// <typeparam name="T"></typeparam>

                   /// <param name="item"></param>

                   public static void Remove<T>(T item) where T : class

                   {

                             try

                             {

                        // Crée une instance de l’objet avec le type passé en argument

                                      Type tType = item.GetType();

                                      Object newObj = Activator.CreateInstance(tType, new 

                        object[0]);

 

                        // Récupère les proprieties de l’objet passé en argument

                                      PropertyDescriptorCollection originalProps =

                        TypeDescriptor.GetProperties(item);

 

                        // Copie le contenue de la donnée passée en argument

                        // au nouveau objet du meme type

                                      foreach (PropertyDescriptor currentProp in

                        originalProps)

                                      {

                                                if (currentProp.Attributes[typeof(

                              System.Data.Linq.Mapping.ColumnAttribute)] !=

                              null)

                                                {

                                                          object val = currentProp.GetValue(item);

                                                          currentProp.SetValue(newObj, val);

                                                }

                                      }

 

                        //lie l’objet à la table, appel de la function deleteOnSubmit

                        // et soumet les changement à la base.

                                      using (NWindDataContext context = new

                        NWindDataContext())

                                      {

                                                var table = context.GetTable<T>();

                              table.Attach((T)newObj, true);

                                                table.DeleteOnSubmit((T)newObj);

                                                context.SubmitChanges();

                                      }

                             }

                             catch (Exception)

                             {

                                      throw;

                             }

                   }

 

Utilisation :

Une fois l’EntityCrud créé vous pouvez l’utiliser comme dans les exemples suivant :

Exemple Insert :

           try

                             {

                             Customer c = new Customer();

                             c.CustomerID = "AAAAA";

                             c.Address = "554 Westwind Avenue";

                             c.City = "Wichita";

                             c.CompanyName = "Holy Toledo Bibles";

                             c.ContactName = "Frederick Flintstone";

                             c.ContactTitle = "Boss";

                             c.Country = "USA";

                             c.Fax = "316-335-5933";

                             c.Phone = "316-225-4934";

                             c.PostalCode = "67214";

                             c.Region = "EA";

 

                             Order_Detail od = new Order_Detail();

         od.Discount = .25f;

                             od.ProductID = 1;

                             od.Quantity = 25;

                             od.UnitPrice = 25.00M;

 

                             Order o = new Order();

                             o.Order_Details.Add(od);

                             o.Freight = 25.50M;

                             o.EmployeeID = 1;

                             o.CustomerID = "AAAAA";

 

                             c.Orders.Add(o);

 

                             EntityCrud.Insert<Customer>(c); //INSERT Customer mais aussi les autres champs qui lui sont associés (order, order_detail).

 

                             }

                             catch (Exception ex)

                             {

                                      MessageBox.Show(ex.Message);

                             }

 

Les données seront toutes insérée en base : Le Customer, L’order et l’order_detail avec seulement cet appel : EntityCrud.Insert<Customer>(c);

Exemple Delete :

                             try

                             {

                             Customer doa = EntityCrud.SelectByPK<Customer>("AAAAA");

                                     EntityCrud.Remove<Customer>(doa); //DELETE

                             }

                             catch (Exception ex)

                             {

                                      MessageBox.Show(ex.Message);

                             }

Exemple Update :

 

         try

                             {

                                      var q = EntityCrud.SelectByPK<Customer>("AAAAA");

 

                                      q.City = "Calgary";

                                      q.Country = "Canada";

                                      q.Address = "100 Longhorn Drive";

                                      q.CompanyName = "Holy Moses Bibles";

                                      q.Region = "EE";

                                      q.ContactTitle = "Gopher";

                                      q.ContactName = "Moxie Peroxide";

 

                                      EntityCrud.Update<Customer>(q);//UPDATE

                             }

                             catch (Exception ex)

                             {

                                      MessageBox.Show(ex.Message);

                             }

 

ADO.NET, ASP.NET AJAX, C#, WPF , , , ,

ASP.NET AJAX 4 : Exemple d’utilisation

8. June 2009 by Davy.Houareau

Nous allons aborder dans cet article un exemple d’utilisation de l’ASP.NET AJAX 4. Cet exemple consiste en l’affichage de produits en fonction de la catégorie sélectionnée. Tout en avançant, nous allons nous attarder sur le fonctionnement de ce nouvel aspect d’ASP.NET AJAX.

Le but de cette nouvelle approche de l’AJAX avec l’ASP.NET est de faire en sorte que le serveur se concentre plus sur l’aspect « donnée » que sur l’aspect « présentation ». En effet, il est possible de faire un site web qui interagit avec une base de données sans aucun contrôle serveur très facilement. C’est sur ce modèle que notre exemple d’utilisation portera (Client « pur » AJAX). On avait l’habitude d’utiliser un script manager couplé à un update panel pour faire de l’AJAX avec nos contrôles serveur et cette technique de rendement partiel nous rendait déjà la tâche assez facile puisqu’on avait de l’AJAX sans patauger dans le JAVASCRIPT. Maintenant, on peut faire de l’AJAX tout aussi simplement sans script manager et sans update panel. Nous verrons ce que sont les nouveaux contrôles client « DataView » et « DataContext »

1. Préparatifs (Exposition de la base de données)

Dans cet exemple nous allons utiliser un ADO.NET Data Service pour interagir avec la base de données, mais il est possible d’utiliser un ASMX ou encore WCF. Les possibilités sont multiples à conditions que le service soit compatible JSON (JavaScript Object Notation -> un format d’échange de données). Commençons par créer un nouveau projet « ASP.NET Web Application » puis ajoutons un ADO.NET Entity Data Model qui pointe sur la base de données que nous intéresse (ici Northwind). Appelons le, ADOEntityDataModel.

Maintenant nous allons pouvoir exposer cet Entity Data Model via un ADO.NET Data Service que nous appellerons MyWebDataService. Puis nous pourrons modifier MyWebDataService.svc pour pouvoir lui associer l’Entity Data Model.

Il faut modifier l’héritage en rajoutant le nom de classe de la source de données. Ici NorthwindEntities. Par défaut c’est le [nom_de_la_base_de_données+Entities]. Vous trouverez ce nom dans le fichier designer de l’Entity Data Model. Notre Entity Data Model est maintenant associé au service mais nous n’avons pas défini ce qui était exposé et de quelle manière. Pour cela nous utiliserons config.SetEntitySetAccessRule("*", EntitySetRights.All);. « * » pour dire que tout est visible et EntitySetRights.All pour autoriser la lecture et l’écriture.

Ce qui donne le code ci-dessous :

public class MyWebDataService : DataService<NorthwindEntities>

    {

        // This method is called only once to initialize service-wide policies.

        public static void InitializeService(IDataServiceConfiguration config)

        {

            config.SetEntitySetAccessRule("*", EntitySetRights.All);

        }

    }

Certes pour notre exemple la sécurité n’est pas optimale mais nous voici avec une base de données exposée assez simplement. Il ne nous reste plus qu’a nous concentrer sur l’ASP.NET AJAX 4 :).

2. Utilisation

Puisque notre exemple porte sur un client « pur » AJAX, supprimons le fichier Default.aspx et ajoutons un simple fichier html que nous nommerons categories.htm. Pour pouvoir utiliser ASP.NET AJAX 4 nous avons besoin de quelques fichiers JavaScript que vous pouvez trouver sur CODEPLEX.

3 Fichiers sont nécessaires:

  • MicrosoftAjax.js (base obligatoire)
  • MicrosoftAjaxAdoNet.js (on utilise de l’ADO.NET)
  • MicrosoftAjaxTemplates.js (pour avoir des templates où nous afficherons les données)

Ajoutons donc un répertoire « Scripts » et nos 3 fichiers JavaScript puis incluons-les dans notre page categories.htm.

Ce n’est pas tout, il nous faut aussi ajouter deux namespaces:

  • xmlns:sys="javascript:Sys"
  • xmlns:dataview="javascript:Sys.UI.DataView" (nous verrons un peu plus tard le Dataview)
  • sys:activate="*" (activer tous les éléments de la page pour le support d’attributs sys, On l’utilisera pour la sélection)

Notre page categories.html ressemble donc à ceci :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >

<head>

    <title>Categories</title>

    <link type="text/javascript" href="Content/Scripts/MicrosoftAjax.js" />

    <link type="text/javascript" href="Content/Scripts/MicrosoftAjaxAdoNet.js" />

    <link type="text/javascript" href="Content/Scripts/MicrosoftAjaxTemplates.debug.js" />

</head>

<body xmlns:sys="javascript:Sys"

      xmlns:dataview="javascript:Sys.UI.DataView"

      sys:activate="*">

</body>

</html>

Maintenant, nous sommes prêts à aborder le template client.

Grâce à ASP.NET AJAX 4, presque n’importe quelle balise html peut devenir un template dans le lequel on pourra faire du binding. Pour qu’une balise soit reconnue en tant que template, il faut qu’elle possède l’attribut class="sys-template". Ce nom est une convention et doit être utilisé simplement parce que le moteur de rendu cherchera ce nom de classe pour manipuler le template. Il est conseillé de définir cette classe dans le CSS en tant que display:none;. Lors de l’affichage, ce style sera modifié automatiquement afin de voir le résultat.

Pour notre exemple, notre template sera une balise tbody.

    <table cellspacing="0">

        <thead>

            <tr><th colspan="2">Categories</th></tr>

            <tr><th>Name</th><th>Description</th></tr>

        </thead>

        <tbody class="sys-template">

        </tbody>

    </table>

.sys-template

{

    display: none;

}

Une fois la balise reconnue en tant que template, il ne reste plus qu’à faire du binding et il est possible d’en faire n’importe où à l’intérieur du template. Il existe deux écritures possibles : une courte, une longue. L’écriture courte se contente de rendre la valeur de la propriété de l’objet telle quelle et elle se présente de cette façon : {{ MyProperty }}. Ce type de binding se fait dans un seul sens et une seule fois (one-time binding). En ce qui concerne l’écriture longue, l’équivalent de {{ MyProperty }} est : {binding MyProperty, mode=oneWay}. L’écriture longue permet de faire du binding bidirectionnelle et d’utiliser une fonction pour « convertir » le rendu de la Valeur. Pour faire du bidirectionnel, il suffit de { binding MyProperty }. Quand on parle ici de binding bidirectionnelle, c’est au sein du même contexte.

Le template fourni également des pseudo-colonnes prédéfinies qui permettent d’accéder à des valeurs bien utiles comme $index, $dataitem, $id, $element.

  • $index : l’index de l’élément courant. (pratique un style alternatif)
  • $dataitem : l’élément courant
  • $id("ID") : créer ou accède à un identifiant unique pour l’élément en court. La valeur sera le nom spécifié + l’index (ici « ID3 » par exemple)
  • $element : récupère la balise dans laquelle ce mot clé est spécifié (ex :<div>{{$element}}</div>retournera la balise div).
C’est le bon moment pour aborder les deux nouveaux contrôles client que nous apporte ASP.NET AJAX 4 : le DataView et le DataContext.

Le DataContext est un proxy qui va communiquer avec un service sachant utiliser des données sous forme de JSON. C’est lui qui fera la liaison entre le client et le service.

Voici comment le déclarer :

<script type="text/javascript">

        var dataContext = $create(

            Sys.Data.AdoNetDataContext, { serviceUri: "MyWebDataService.svc" });

</script>

Vu que nous utilisons ADO.NET nous sommes obligés de déclarer notre DataContext de cette façon avec Sys.Data.AdoNetDataContext.

Le DataView, quant à lui, doit être associé à un template. Il peut traiter une collection d’éléments en répliquant l’instance de son template pour chaque élément de la collection. Il peut être utilisé pour un affichage sous forme de détail ou de liste. Il possède pas mal de points important pour sa configuration. Nous ne verrons que ceux dont nous aurons besoin.

Pour notre exemple voici à quoi doit ressembler notre balise tbody :

<tbody class="sys-template"

               sys:attach="dataview"

               dataview:sys-key="master"

               dataview:dataprovider="{{dataContext}}"

               dataview:autofetch="true"

               dataview:fetchoperation="Categories"

               dataview:selecteditemclass="rowSelected"

               dataview:fetchparameters="{{ {$expand: 'Products'} }}"

               dataview:initialselectedindex="0">

</tbody>

Quelques explications s’imposent non ?

  • sys:attach="dataview" : permet de déclarer le DataView
  • dataview:sys-key="master" : permet d’identifier le DataView (ici notre DataView s’appelle master)
  • dataprovider="{{dataContext}}" : permet de binder le DataView au DataContext déclaré plus haut.
  • dataview:autofetch="true" : récupérer les données au chargement de la page.
  • dataview:fetchoperation="Categories" : La fonction du service à appeler pour récupérer les données
  • dataview:selecteditemclass="rowSelected" : style pour la ligne sélectionnée.
  • dataview:fetchparameters="{{ {$expand: 'Products'} }}" : fetchparameters pour passer des paramètres à la requête et ici $expand permet de récupérer les éléments de la propriété de navigation « Products » de l’Entity Set « Categories » (Ce qui nous servira pour afficher les produits des catégories plus tard).
  • dataview:initialselectedindex="0" : Présélectionner un élément par défaut.

Il nous manque maintenant le binding. Vu que nous n’avons pas besoin ici de binding bidirectionnel, nous utiliserons l’écriture courte.

Voici à quoi notre binding ressemble :

<tr sys:command="select" class:alternaterow="{{ $index % 2 != 0 }}">

    <td>

        {{ CategoryName }}

    </td>

    <td>

        {{ Description }}

    </td>

</tr>

Notez la présence deux attributs « spéciaux » :

  • sys:command="select" : permet la sélection de l’élément.
  • class:alternaterow="{{ $index % 2 != 0 }}" : petite astuce pour avoir un style alternatif. Le style ici sera alternaterow.

N’oublions pas de rajouter ces classes dans le style :

.rowSelected

{

    color:Red;

}

.alternaterow

{

    background-color:#A3B8FF;

}

Il ne nous reste plus qu’a nous occuper de l’affichage des produits. Là aussi, il nous faut utiliser un DataView que nous lierons au premier.

Voici le DataView pour l’affichage des produits :

<table cellspacing="0">

        <thead>

            <tr>

                <th colspan="2">

                    Products

                </th>

            </tr>

            <tr>

                <th>

                    Name

                </th>

                <th>

                    Price

                </th>

            </tr>

        </thead>

        <tbody id="product-template" class="sys-template" sys:attach="dataview" dataview:data="{binding selectedData.Products, source={{master}} }">

            <tr>

                <td>

                    {binding ProductName}

                </td>

                <td>

                    {binding UnitPrice}

                </td>

            </tr>

        </tbody>

    </table>

Ici, la seule nouveauté c’est : dataview:data="{binding selectedData.Products, source={{master}} }" : Tout simplement pour indiquer que les données qui vont être utilisées par ce DataView proviennent de l’élément sélectionné dans le DataView « master ».

Et nous voici au résultat tant attendu :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Categories</title>

    <link href="Style/style.css" rel="stylesheet" type="text/css" />

    <script src="Scripts/MicrosoftAjax.js" type="text/javascript"></script>

    <script src="Scripts/MicrosoftAjaxTemplates.js" type="text/javascript"></script>

    <script src="Scripts/MicrosoftAjaxAdoNet.js" type="text/javascript"></script>

    <script type="text/javascript">

        var dataContext = $create(Sys.Data.AdoNetDataContext, { serviceUri: "MyWebDataService.svc" });

    </script>

    <style type="text/css">

        .sys-template

        {

            display: none;

        }

        .rowSelected

        {

            color:Red;

        }

        .alternaterow

        {

             background-color:#A3B8FF;

        }

    </style>

</head>

<body xmlns:sys="javascript:Sys"

      xmlns:dataview="javascript:Sys.UI.DataView"

      sys:activate="*">

       

    <table cellspacing="0">

        <thead>

            <tr>

                <th colspan="2">

                    Categories

                </th>

            </tr>

            <tr>

                <th>

                    Name

                </th>

                <th>

                    Description

                </th>

            </tr>

        </thead>

        <tbody class="sys-template"

               sys:attach="dataview"

               dataview:sys-key="master"

               dataview:dataprovider="{{dataContext}}"

               dataview:autofetch="true"

               dataview:fetchoperation="Categories"

               dataview:selecteditemclass="rowSelected"

               dataview:fetchparameters="{{ {$expand: 'Products'} }}"

               dataview:initialselectedindex="0">

            <tr sys:command="select" class:alternaterow="{{ $index % 2 != 0 }}">

                <td>

                    {{ CategoryName }}

                </td>

                <td>

                    {{ Description }}

                </td>

            </tr>

        </tbody>

    </table>

    <table cellspacing="0">

        <thead>

            <tr>

                <th colspan="2">

                    Products

                </th>

            </tr>

            <tr>

                <th>

                    Name

                </th>

                <th>

                    Price

                </th>

            </tr>

        </thead>

        <tbody id="product-template" class="sys-template" sys:attach="dataview" dataview:data="{binding selectedData.Products, source={{master}} }">

            <tr>

                <td>

                    {binding ProductName}

                </td>

                <td>

                    {binding UnitPrice}

                </td>

            </tr>

        </tbody>

    </table>

</body>

</html>

3.Conclusion

Comme vous avez pu le constater, nous avons pu réaliser un client « pure » AJAX en écrivant deux lignes de JAVASCRIPT grâce a ASP.NET AJA 4 . Evidement nous avons abordé un exemple précis mais cette nouvelle façon de faire de l’AJAX offre beaucoup d’autres possibilités d’utilisation.

ADO.NET, ASP.NET AJAX

Abstraction des Accès BDD dans une Factory en ADO .NET

Ecrit par Thomas COVILLE tutoré par Jérémie BERTRAND

    Dans le cadre du développement d’une application reposant sur l’utilisation d’une base de données, il est important de penser à la réutilisabilité de son code. Il n’est pas question de réécrire toute une application à chaque changement de moteur de BDD. Une des solution est donc de rajouter une couche intermédiaire entre la partie business et la base de donnée. Dans le pire des cas, seule cette couche est à réécrire.

    En allant plus loin, il est possible d’intégrer les accès base dans une fabrique abstraite (factory). Microsoft nous a mâché le travail, depuis le framework .Net 2.0 le namespace System.Data.Common contient tout ce qu’il nous faut pour le faire facilement. Voici rapidement les types les plus important contenus dans ce namespace:

DbProviderFactories

Conteneur de Factory. C’est à travers ce type, que l’on va pouvoir binder notre architecture abstraite à un provider spécifique.

DbProviderFactory

Type implémenté pour chaque provider, c’est à travers un objet de ce type que l’on pourra instancier tous les types du provider choisi.

DbConnection

Type gérant la connexion à la base.

DbCommand

Type permettant d’envoyer des commandes (paramétrées ou nom) dans une DbConnection.

DbDataReader

Retour de l’exécution d’une DbCommand (un peu l’équivalent des bons vieux recordsets d’antan…)

Providers

Microsoft a déjà intégré un certain nombre de providers se conformant à cette architecture :
  • System.Data.SqlClient (SQL Server)
  • System.Data.OleDb (MS Access)
  • System.Data.OracleClient (Oracle DataBase)

D’autre providers sont disponibles sur internet, pour connaître ceux dont vous disposez sur votre machine vous pouvez soit jeter un oeil dans le fichier machine.config du framework à la section <DbProviderFactories>. Soit appeler la Méthode static  GetFactoryClasses() du type DbConnectionFactory :

DataTable tab =  DbProviderFactories.GetFactoryClasses();

foreach (DataRow row in tab.Rows)
{
foreach (DataColumn col in tab.Columns)
{
System.Console.WriteLine("{0} = {1} ;", col.ColumnName, row[col]);
}
Console.WriteLine(Environment.NewLine);
}

 

utilisation des DbProviderFactories

Entrons un peu plus dans le détail maintenant que nous avons une vue générale de ce que Microsoft à mis à notre disposition. nous allons donc réaliser une Classe nommée ConnectDb  encapsulant l’utilisation des types vus précédemment :

image_thumb4 

     La classe ConnectDb contient un ensemble de méthodes statiques qui couvrent les opérations de base pour accéder à une base de données.

    Personnellement j’ai choisi de la définir en abstract puisque toutes ses méthodes sont statiques et qu’elle n’a pas vocation à être instanciée. On aurait pu mettre le constructeur par défaut en privé, le résultat serait le même, je pense que c’est surtout une question de goût…

Voyons en détail l’implémentation de cette classe.

 

 

 

Implémentation

Gestion des paramètres de connexion

    Pour ma part j’ai choisi de stocker la chaîne de connexion, ainsi que le provider choisi dans un fichier de configuration (Pour savoir comment créer votre propre fichier de configuration,vous pouvez consulter ce billet).

    Voici à quoi ressemble mon fichier App.config :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="MyDefaultConnectionString"
connectionString="Data Source=MYSERVER\SQLEXPRESS;Initial Catalog=MyDataBase;Integrated Security=True;Pooling=False"/>
</connectionStrings>
<appSettings>
<add key="MyDefaultProvider" value="System.Data.SqlClient"/>
</appSettings>
</configuration>

     Pour mon exemple j’ai choisi d’utiliser une base de données SQL Server et le provider correspondant : Sytem.Data.SqlClient . Bien entendu, la suite du code fonctionnera avec n’importe quel provider implémentant les Types abstraits du namespace System.Data.Common.

static readonly String _connectionString;
static readonly String _provider;

static ConnectDb()
{
_connectionString = ConfigurationManager.ConnectionStrings["MyDefaultConnectionString"].ConnectionString;
_provider = ConfigurationManager.AppSettings["MyDefaultProvider"].ToString();
}

    J’ai défini un constructeur statique pour récupérer les valeurs de la chaîne de connexion et le provider choisi depuis le fichier de configuration. Un constructeur static est appelé automatiquement à la première référence du type. Il ne nécessite donc pas d’instanciation explicite .

Utilisation de la factory

    La récupération de la factory pour un provider spécifique se fait à l’aide de la classe DbProviderFactories. Pour ma part j’ai choisi de l’encapsuler dans une Propriété “Factory” :

protected static DbProviderFactory Factory
{ get
{
return DbProviderFactories.GetFactory(_provider);
}
}

    A ce stade nous disposons donc d’une instance d’un type implémentant DbProviderFactory, spécifique au provider que nous avons choisi. (dans mon cas il s’agit en interne d’une instance de  System.Data.SqlClient.SqlClientFactory).

    Parmi l’ensemble  des méthodes que nous propose l’objet DbProviderFactory , nous n’en utiliserons que deux dans le cadre de cet article :

//Crée une instance d'un objet de type DbCommand
Factory.CreateCommand();
//Crée une instance d'un objet de type DbConnection
Factory.CreateConnection();

   voyons comment les utiliser :

Connexion à la Base de données

    Rien de bien compliqué ici, on appelle la méthode CreateConnection() de la factory, puis on affecte la Chaîne de connection à l’objet DbConnection que l’on a récupéré.

protected static DbConnection GetConnection()
{
DbConnection conn = Factory.CreateConnection();
conn.ConnectionString = ConnectionString;
return conn;

}

    Attention, l’objet connexion retourné est FERME. la connexion à la base n’est donc pas active. J’ai choisit de ne pas retourné d’objet systématiquement ouvert pour ne pas risquer d’oublier de le refermer. Cela permet donc de garder le contrôle sur le pool de connexion ouvertes.

Manipulation des objets DbCommand

    Création

    Une fois la connexion à la base de donnée gérée, il ne reste plus qu’à envoyer les requêtes. C’est là que l’objet DbCommand entre en jeu. Il y a 2 façons de créer un objet commande: en utilisant la factory, ou en passant par un objet DbConnection :

  • Solution 1
public static DbCommand CreateCommand()
{
DbCommand cmd = Factory.CreateCommand();
return cmd;
}
  • Solution 2
public static DbCommand CreateCommand()
{
DbConnection conn = GetConnection();
DbCommand cmd = conn.CreateCommand();
return cmd;
}

    Pour ma part je préfère la première version. C’est encore une fois assez discutable, la raison est que je préfère garder mes objets indépendants les uns des autres le plus longtemps possible.

Envoi d’une requête

   Une fois notre Objet DbCommand créé nous pouvons lui affecter une requête simple à l’aide de la propriété CommandText (nous verrons par la suite comment faire pour créer des commandes paramétrées") :

DbCommand cmd = ConnectDb.CreateCommand();
cmd.CommandText = "SELECT Champ1, Champ2 FROM MATABLE WHERE Champ3='toto'";

   Attention : La syntaxe des requêtes SQL peut différer d’un SGBD à l’autre, c’est pourquoi, si l’on veut conserver la souplesse de notre factory, l’affectation d’une requête SQL à une DbCommand doit être effectuée EN DEHORS de la classe ConnectDb, par exemple dans une classe de DAO classique.

Nous pouvons regrouper les différents types de requête en 3 catégories :

  • Les requêtes de Sélection qui renvoient un ensemble de données (SELECT .. FROM… JOIN.. WHERE)
  • Les ordres DDL qui renverront la plupart du temps un nombre d’enregistrements affectés (Update, Delete,Insert…)
  • Les requêtes Scalaires qui retournent un champ unique dans un enregistrement unique (ex : “SELECT Count(*) FROM matable”, “SELECT ORACLE_SEQUENCE.NEXTVAL FROM dual”)

    Typiquement nous pouvons donc créer 3 méthodes de requêtage pour gérer les différents cas. Le code de ces méthodes diffère assez peu, dans tous les cas, il nous faut récupérer un objet DbConnection, l’ouvrir et l’affecter à l’objet DbCommand contenant la requête.

Comme un exemple vaut mieux qu’un long discours, voici l’implémentation de ces fonctions :

  • ExecuteQuery (requêtes de Sélection)
public static DbDataReader ExecuteQuery(DbCommand cmd)
{
DbDataReader ret;
DbConnection conn = GetConnection();
conn.Open();
cmd.Connection = conn;
cmd.cr
ret = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);

return ret;
}

    L’objet DbDataReader est un Curseur sur un ensemble d’enregistrement en provenance de la base de donnée en mode connecté, un peu à la manière des vieux recordset. c’est donc dans cet objet que sont renvoyés les enregistrements sélectionnés par la commande.

     Le paramètre passé à la méthodes ExecuteReader() de l’objet DbCommand est une énumération qui  permet de s’assurer que la connexion sera fermée en même temps que le DataReader. En effet, pour que ce dernier fonctionne correctement, la connexion sur laquelle il repose doit rester ouverte jusqu’au dernier moment. On ne peut donc pas fermer la connexion immediatement après la création du DataReader.

  • ExecuteNonQuery (ordres DDL)

public static int ExecuteNonQuery(DbCommand cmd)
{
int ret;
using (DbConnection conn = GetConnection())
{
conn.Open();
cmd.Connection = conn;
ret = cmd.ExecuteNonQuery();
conn.Close();
return ret;
}
}

     Ici le code est assez similaire à la méthode précédente, on notera cette fois la présence du bloc using autour de l’objet DbConnection. En effet Microsoft à eu la bonne idée d’implémenter IDisposable sur ces objets. Ainsi, dans le cas de l’objet DbConnection, on est sûr que quoiqu’il arrive, la méthode Dispose() sera appelée, et que la connexion à la base, fermée.

  • ExecuteScalar (requêtes Scalaires)
public static object ExecuteScalar(DbCommand cmd)
{
object ret;
using (DbConnection conn = GetConnection())
{
conn.Open();
cmd.Connection = conn;
ret = cmd.ExecuteScalar();
conn.Close();
return ret;
}
}

    Encore une fois, le code ne diffère pas énormément de la méthode ExecuteQuery(), la seule chose à noter est que le type de retour de la méthode est de type object. il vous faudra donc penser lors de l’utilisation, à effectuer un cast sur la valeur retournée en fonction du type que vous attendez.

    Maintenant que nous avons nos 3 méthodes, l’exécution d’une requête est simplissime :

ex:

//requête simple
DbCommand cmd = ConnectDb.CreateCommand();
cmd.CommandText = "SELECT * FROM Personne where prenom = ‘toto’";

DbDataReader reader = ConnectDb.ExecuteQuery(cmd);
while (reader.Read())
{
System.Console.WriteLine("Nom : {0} ,Prenom : {1} , mail : {2}",reader["nom"],reader["prenom"],reader["mail"]);
}
reader.Close();

     Le parcours d’un objet DbDataReader se fait grâce à la méthode booléenne Read(). A la construction de l’objet le curseur pointe AVANT le tout premier enregistrement. A chaque appel de Read(), le curseur passe à l’enregistrement suivant, si l’on est arrivé à la fin du DataReader ou si la requête n’a aucun résultat , Read() renvoi False.

    Pour accéder aux champs des enregistrement, vous pouvez soit utiliser la méthode “tableaux” à l’aide de crochets : reader[“nomColonne”] ou reader[NumColonne"], sans oublier de caster correctement les valeurs de retour.

    Ou bien encore utiliser toute les méthodes reader.GetXXXX() pour récupérer directement une valeur typée.

     Nous avons vu qu’il était très simple d’envoyer une requête à la base de données, cependant il peut parfois être utile de paramétrer une requête, c’est à dire, dissocier les valeurs des champs. Cela permet un traitement plus automatisé, plus optimisé et plus sûr des requêtes, notamment au niveau des attaques par injection de code SQL. Voyons ensemble comment écrire de telles requêtes.

Création d’une requête paramétrée

     Créer une requête paramétrée n’est pas bien plus difficile que de créer une requête sans paramètre : reprenons un objet DbCommand, et voyons quelles autres méthodes s’offrent à nous :

     Nous avons une Méthode CreateParameter() et une propriété Parameters qui contient la liste de tous les paramètres associés à cette commande.

    Avant d’associer des paramètres à une DbCommand, il faut tout d’abord référencer ces paramètres dans la requête elle même. cela se fait grâce à un mot clé choisi par vous-même, précédé d’un symbole qui indiquera au provider que la chaîne de caractère suivante est un paramètre. Ce symbole varie d’un provider à l’autre, voici un tableau récapitulant ceux que je connais :

Oracle ‘:’ + NomParametre
SqlServer ‘@’+ NomParametre
Access ‘?’+ NomParametre

    Il nous faut ensuite créer les paramètres à proprement parler et leur donner leur valeur. rien de plus simple, on crée un objet DbParameter à l’aide de la méthode CreateParameter de la DbCommand et on affecte les propriétés suivantes :

  • ParameterName : chaîne de caractères correspondant au paramètre dans la requête (sans le symbole)
  • Value : valeur à affecter au paramètre
  • DbType (facultatif) : Type à affecter au paramètre

Puis on ajoute le paramètre créé à la Liste des paramètres de la commande. A partir de là la commande s’exécute exactement comme une commande normale, à l’aide des 3 méthodes que nous avons définies plus haut.

Exemple d’une requête d’insertion: on suppose que l’on dispose d’une table personne dans notre base de données :

image1_thumb

//requête Parametrée
DbCommand cmd2 = ConnectDb.CreateCommand();
cmd2.CommandText = "INSERT INTO Personne (mail,nom,prenom)VALUES (@mail,@nom,@prenom)";
DbParameter mailParam = cmd2.CreateParameter();
mailParam.ParameterName = "mail";
mailParam.Value = "uneadressemail@kkpart.com";
cmd2.Parameters.Add(mailParam);




DbParameter nomParam = cmd2.CreateParameter();
nomParam.ParameterName = "nom";
nomParam.Value = "COVILLE";
cmd2.Parameters.Add(nomParam);



DbParameter prenomParam = cmd2.CreateParameter();
prenomParam.ParameterName = "prenom";
prenomParam.Value = "Thomas";
cmd2.Parameters.Add(prenomParam);


ConnectDb.ExecuteNonQuery(cmd2);

    Et voilà, vous disposez désormais d’une classe complète permettant de gérer tout vos accès base en mode connecté, de façon totalement indépendante du SGBD choisi !

    Nous pouvons cependant aller encore plus loin grâce à la généricité. En effet pourquoi se contenter d’utiliser un DbDataReader, quand on peut facilement disposer d’une Liste d’objets directement prêts à l’usage ? C’est ce que nous allons faire de suite :

La Methode Find<T>(…)

Implémentation

    L’objectif de cette méthode, est d’obtenir, en se basant sur une DbCommand on ne peut plus classique, une liste d’objets de type T  (générique donc).

    Pour ce Faire nous allons utiliser la notion de delegate et plus particulièrement le Type Func<Targ0, TResult> ajouté dans le framework 3.5 . Grosso modo, c’est une référence vers une fonction qui prendrait un paramètre de type Targ0 et renverrait un objet de type TResult.

    L’intérêt pour nous, c’est que nous allons donc pouvoir ajouter en paramètre de notre méthode Find, un delegate qui prendrait en Parametre un DbDataReader et renverrait un objet de Type T  remplit avec les données du DbDataReader. Pour faire simple, le delegate serait un “traducteur” pour passer d’un DbDataReader, vers une List<T>.

Voici donc à quoi ressemble notre Méthode Find :

public static List<T> Find<T>(DbCommand cmd, Func<DbDataReader, T> ParseObject)
{
using (DbDataReader rs = ExecuteQuery(cmd))
{
List<T> retList = new List<T>();

while (rs.Read())
{
T oRow = ParseObject(rs);
retList.Add(oRow);
}
return retList;
}
}

   Tout comme le type DbConnection,  le type DbDataReader implémente IDisposable. On ne se privera donc pas de l’entourer d’une directive using, pour s’assurer que les connections à la base seront correctement fermées. A l’aide de la méthode ExecuteQuery() que nous avons codée précédemment, nous récupérons le DbDataReader correspondant à notre DbCommand.

   Puis, pour chaque Enregistrement du DataReader, nous appelons le delegate passé en paramètre à notre méthode find  (Parseobject()). Il ne nous reste plus qu’à ajouter l’objet de Type T à la List<T> et à retourner cette liste une fois le reader entièrement parcouru.

   Très bien me direz vous, mais comment ça s’utilise ce machin ? démonstration :

Utilisation

   Considérons tout d’abord que dans notre Base de données, nous avons une table Personne contenant les champs suivants :

   Reprenons l’exemple de notre table Personne :

image11

      Nous réalisons une classe Personne dans notre programme pour la manipulation des données :

public class Personne
{
public String NomPrenom
public String Mail;

}

      J’ai fait exprès de Concaténer dans ma Classe les champs Nom et Prenom et d’omettre le champ Age,  pour bien montrer que les structure des classes du programme peuvent être différente de celles des tables de la base.

nous créons notre requête de sélection :

DbCommand cmd = ConnectDb.CreateCommand();
cmd.CommandText = "SELECT * FROM Personne";

    Il ne reste plus qu’à appeler notre fameuse méthode Find() . Avant cela je dois introduire la notion d’Anonymous Delegate. Grâce à ce concept nous allons pouvoir passer directement à notre Func<DbDataReader,T>, un bloc de code qui sera interprété comme une fonction :

List<Personne> personnes = ConnectDb.Find(cmd, delegate(DbDataReader rs)
{
Personne p = new Personne();
p.mail = rs["mail"].ToString();
p.NomPrenom = rs["Nom"].ToString() + rs["prenom"].ToString();
return p;
});

    Le code est donc dérisoirement simple, on passe donc en paramètre au find notre delegate et directement à la ligne, grâce au délégué anonyme nous mettons notre bloc de traitement.

    Remarquez une chose assez intéressante, je n’ai pas spécifié le type générique utilisé pour la méthode Find. Alors que pourtant, le prototype de la fonction est bien Find<T>(…). En fait, le compilateur C# est “intelligent”, et déduit du code, le type générique utilisé et est capable de déterminer si il est correct ou non. Ce mécanisme s’appelle Inférence de Type.

   Rentrons maintenant dans une amélioration introduite par le framework 3.5, et que permet d’utiliser Func<Targ,TResult> : Les lambda Expressions. L’explication détaillée des lambda expressions n’entre pas dans le cadre de cet article, je me permet juste de vous montrer comment aurait pu s’écrire l’appel à la méthode Find en utilisant ce mécanisme, libre à vous d’approfondir vos recherches sur ce sujet :

List<Personne> personnesLambda = ConnectDb.Find(cmd, rs => new Personne()
{
mail = rs["mail"].ToString(),
NomPrenom = rs["Nom"].ToString() + rs["prenom"].ToString()
});

     Les lambda expressions reposent en grande partie sur l’Inférence de type dont je vous parlais plus haut.  Décomposons les grandes lignes de ce code :

    Tout d’abord le rs => new Personne(). J’ai volontairement conservé les nom des variables pour que l’exemple soit plus clair:

     Par inférence de type, le compilateur déduit que la variable rs doit être de type DbDataReader. la flèche (=>) introduit ensuite une ligne de code, devant nécessairement retourner dans notre cas, un objet de Type Personne (toujours par inférence de type). Je n’ai donc qu’à instancier mon objet personne et à initialiser ses membres publics.

      Ceci n’est qu’un exemple extrêmement simpliste de ce qu’il est possible de faire à l’aide des lambda Expressions, si vous souhaitez approfondir cette voie, je vous conseille d’effectuer des recherches sur LINQ introduit également au framework 3.5, au sujet duquel j’écrirais sans doute un article sous peu.

Conclusion

    Nous avons donc désormais entre les mains, une classe permettant d’abstraire tous les accès à une base de données, assez souple pour interagir avec n’importe quel provider .NET implémentant les classes du namespace System.Data.Common.

   Pour conclure, je joint à cet article le code source (librement utilisable, distribuable, commercialisable, etc) complet de cette classe avec les exemples que nous avons vu, et reste à votre entière disposition si vous avez des questions, ou simplement envie de débattre d’une autre méthode d’implémentation.

  Notez bien que tout le code que nous avons écrit ne manipule QUE les objets abstraits du namespace System.Data.Common. EN AUCUN CAS la classe ConnectDb ne doit contenir de référence à un provider spécifique. Nous perdrions d’emblée tout l’intérêt de l’utilisation d’une factory.

Thomas COVILLE

Blog : http://thoums.blogspot.com

ADO.NET, C#