IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Introduction par l'exemple à Entity Framework 5 Code First


précédentsommairesuivant

V. Etude de cas avec Oracle Database Express Edition 11 g Release 2

V-A. Installation des outils

Les outils à installer sont les suivants :

  • le SGBD : [http://www.oracle.com/technetwork/products/express-edition/downloads/index.html] ;
  • un outil d'administration : EMS SQL Manager for Oracle Freeware [http://www.sqlmanager.net/fr/products/oracle/manager/download] ;
  • un client Oracle pour .NET : ODAC 11.2 Release 5 (11.2.0.3.20) with Oracle Developer Tools for Visual Studio : [http://www.oracle.com/technetwork/developer-tools/visual-studio/downloads/index.html].

Dans les exemples qui suivent, l'utilisateur system a le mot de passe system .

Lançons Oracle [1] puis l'outil [SQL Manager Lite for Oracle] avec lequel on va administrer le SGBD [2].

Image non disponible
  • en [3], nous nous connectons à une base existante ;
Image non disponible
  • en [4], nous utilisons le service Oracle XE pour se connecter ;
  • en [5], on indique le nom de la base XE ;
  • en [6], on se connecte en tant que system / system ;
  • en [7], on termine l'assistant ;
Image non disponible
  • en [8], on se connecte à la base ;
  • en [9], on est connecté ;
  • parce qu'on s'est connecté en tant que l'utilisateur system / system qui a des droits étendus, nous pourrons par exemple gérer les utilisateurs [10] ;
Image non disponible
  • en [11], on crée un nouvel utilisateur ;
  • en [12], il s'appellera [RDVMEDECINS-EF] ;
  • en [13], et aura le mot de passe rdvmedecins  ;
  • en [14], on valide la création de l'utilisateur ;
  • en [15], l'utilisateur a été créé ;
Image non disponible
  • en [16], l'utilisateur [RDVMEDECINS-EF] est également un schéma de base de données ;
  • en [17], l'utilisateur tel qu'il a été créé n'a pas suffisamment de droits. Nous lui en donnons via un script SQL ;
Image non disponible
  • en [18], le script exécuté ;
  • en [19], on va essayer de se connecter sous l'identité de [RDVMEDECINS-EF] afin de voir ce qu'il peut faire. Pour cela, nous commençons par enregistrer une nouvelle base dans [EMS Manager] ;
Image non disponible
  • en [19], on se connecte via le service XE ;
  • en [20], on se connecte sous l'identité RDVMEDECINS-EF / rdvmedecins ;
  • en [21], on donne un alias qui reflète le nom de l'utilisateur connecté ;
  • en [22], on se connecte à Oracle avec les informations données ;
Image non disponible
Image non disponible
  • en [22], on a réussi à se connecter ;
  • en [23], on essaie de créer une table dans le schéma [RDVMEDECINS-EF] ;
  • en [24], on définit une table quelconque ;
  • en [25], on valide sa définition ;
Image non disponible
  • en [26], la table a été créée. On la supprime ;
  • en [27], elle a été supprimée.

Maintenant que nous avons un utilisateur avec suffisamment de droits, nous allons créer le projet VS 2012 qui va créer les tables du schéma [RDVMEDECINS-EF] à partir de la définition des entités.

V-B. Création de la base à partir des entités

Nous commençons par dupliquer le dossier du projet [RdvMedecins-SqlServer-01] dans [RdvMedecins-Oracle-01] [1] :

Image non disponible
  • en [2], dans VS 2012, nous supprimons le projet [RdvMedecins-SqlServer-01] de la solution ;
Image non disponible
  • en [3], le projet a été supprimé ;
  • en [4], on en rajoute un autre. Celui-ci est pris dans le dossier [RdvMedecins-Oracle-01] que nous avons créé précédemment ;
Image non disponible
  • en [5], le projet chargé s'appelle [RdvMedecins-SqlServer-01] ;
  • en [6], on change son nom en [RdvMedecins-Oracle-01]
Image non disponible
  • en [7], on rajoute un autre projet à la solution. Celui-ci est pris dans le dossier [RdvMedecins-SqlServer-01] du projet que nous avons supprimé de la solution précédemment ;
  • en [8], le projet [RdvMedecins-SqlServer-01] a réintégré la solution.

Le projet [RdvMedecins-Oracle-01] est identique au projet [RdvMedecins-SqlServer-01]. Il nous faut faire quelques modifications. Dans [App.config], nous allons modifier la chaîne de connexion et le [DbProviderFactory] qu'on doit adapter à chaque SGBD.

[App.config]
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
<!-- chaîne de connexion sur la base -->
  <connectionStrings>
    <add name="monContexte" connectionString="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User Id=RDVMEDECINS-EF;Password=rdvmedecins;" providerName="Oracle.DataAccess.Client" />
  </connectionStrings>
  <!-- le factory provider -->
  <system.data>
    <DbProviderFactories>
      <remove invariant="Oracle.DataAccess.Client" />
      <add name="Oracle Data Provider for .NET" invariant="Oracle.DataAccess.Client" description="Oracle Data Provider for .NET" type="Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess, Version=4.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
    </DbProviderFactories>
  </system.data>
  • ligne 3 : l'utilisateur et son mot de passe ;
  • lignes 6-11 : le DbProviderFactory . La ligne 9 référence une DLL [Oracle.DataAccess] que nous n'avons pas. On se la procure avec NuGet [1] :
Image non disponible
  • en [2], dans la zone de recherche on tape le mot clé oracle  ;
  • en [3], on choisit le paquetage [Oracle Data Provider] qui va bien. C'est le connecteur ADO.NET d'Oracle ;
Image non disponible
  • en [4], la référence ajoutée ;
  • en [5], dans [App.config], il faut mettre la version correcte de la DLL. On la trouve dans ses propriétés.

Dans le fichier [Entites.cs], il faut adapter le schéma des tables qui vont être générées. Le schéma utilisé est le nom de l'utilisateur propriétaire des tables.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
[Table("MEDECINS", Schema = "RDVMEDECINS-EF")]
  public class Medecin : Personne
  {...}

  [Table("CLIENTS", Schema = "RDVMEDECINS-EF")]
  public class Client : Personne
  {...}

  [Table("RVS", Schema = "RDVMEDECINS-EF")]
  public class Rv
  {...}

  [Table("CRENEAUX", Schema = "RDVMEDECINS-EF")]
  public class Creneau
  {...}

Nous configurons l'exécution du projet :

Image non disponible
  • en [1], on donne un autre nom à l'assembly qui va être généré ;
  • en [2], ainsi qu'un autre espace de noms par défaut ;
  • en [3], on désigne le programme à exécuter.

A ce stade, il n'y a pas d'erreurs de compilation. Exécutons le programme [CreateDB_01]. On obtient l'exception suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Exception non gérée : System.Data.MetadataException: Le schéma spécifié n'est pas valide. Erreurs :
(11,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(23,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(33,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(43,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
   à System.Data.Metadata.Edm.StoreItemCollection.Loader.ThrowOnNonWarningErrors
()
   ...
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15

On se rappelle avoir eu la même erreur avec MySQL. C'est lié au type du champ Timestamp des entités. Nous faisons la même modification. Dans les entités, nous remplaçons les trois lignes

 
Sélectionnez
1.
2.
3.
    [Column("TIMESTAMP")]
    [Timestamp]
    public byte[] Timestamp { get; set; }

par les suivantes :

 
Sélectionnez
1.
2.
3.
    [ConcurrencyCheck]
    [Column("VERSIONING")]
    public int? Versioning { get; set; }

On change donc le type de la colonne qui passe de byte[] à int? . On se rappelle qu'aussi bien pour SQL Server que pour MySQL, la colonne des tables qui servait à gérer la concurrence d'accès recevait une valeur du SGBD à chaque fois qu'une ligne était insérée ou modifiée. A partir de maintenant, nous allons utiliser un champ d'entité qui sera un entier. Dans le SGBD, nous utiliserons des procédures stockées pour incrémenter cet entier d'une unité, à chaque fois qu'une ligne sera insérée ou modifiée.

Nous faisons la modification précédente pour les quatre entités puis nous réexécutons l'application. Nous obtenons alors l'erreur suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
Exception non gérée : System.Data.DataException: An exception occurred while initializing the database. See the InnerException for details. ---> System.Data.ProviderIncompatibleException: DeleteDatabase n'est pas pris en charge par le fournisseur.
   à System.Data.Common.DbProviderServices.DbDeleteDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   ...
   à System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15

La ligne 1 indique que le connecteur ADO.NET d'Oracle n'est pas capable de supprimer la base existante. Rappelons ce qui se passe. Le code de [CreateDB_01.cs] est le suivant :

[CreateDB_01.cs]
Sélectionnez
using System;
using System.Data.Entity;
using RdvMedecins.Models;

namespace RdvMedecins_01
{
  class CreateDB_01
  {
    static void Main(string[] args)
    {
      // on crée la base de données
      Database.SetInitializer(new RdvMedecinsInitializer());
      using (var context = new RdvMedecinsContext())
      {
        context.Database.Initialize(false);
      }
    }
  }
}

La ligne 15 enclenche l'exécution de la classe [RdvMedecinsInitializer] (ligne 12). Celle-ci est la suivante :

public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>

Elle dérive de la classe [DropCreateDatabaseAlways] qui essaie de supprimer puis de recréer la base. Nous changeons la définition de la classe en :

public class RdvMedecinsInitializer : CreateDatabaseIfNotExists<RdvMedecinsContext>

La création de la base n'est faite que si elle n'existe pas. On réexécute [CreateDB_01.cs] et là il n'y a plus d'erreurs. Mais dans [EMS Manager], on constate que la base [RDVMEDECINS-EF] est restée vide. Parce qu'EF 5 a trouvé une base existante, il n'a rien fait. Il ne fait quelque chose que si la base n'existe pas. A partir de là, on tourne en rond. En effet, la chaîne de connexion au SGBD est la suivante :

 
Sélectionnez
1.
2.
3.
  <connectionStrings>
    <add name="monContexte" connectionString="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User Id=RDVMEDECINS-EF;Password=rdvmedecins;" providerName="Oracle.DataAccess.Client" />
</connectionStrings>

Ligne 2, la chaîne de connexion utilise non pas le nom d'une base de données mais le nom d'un utilisateur. Celui-ci doit exister.

Nous sommes amenés alors à construire la base de données [RDVMEDECINS-EF] à la main avec l'outil [EMS Manager for Oracle]. Nous ne décrivons pas toutes les étapes mais simplement les plus importantes.

La base Oracle sera la suivante :

Les tables

Image non disponible
Image non disponible

Les différentes tables ont les clés primaires et étrangères qu'avaient ces mêmes tables dans les deux exemples précédents. Les clés étrangères ont notamment l'attribut ON DELETE CASCADE.

Les séquences

On a créé ici des séquences Oracle. Ce sont des générateurs de nombres consécutifs. Il y en a 5 [1].

Image non disponible
  • en [2], nous voyons les propriétés de la séquence [SEQUENCE_CLIENTS]. Elle génère des nombres consécutifs de 1 en 1, à partir de 1 jusqu'à une valeur très grande.

Toutes les séquences sont bâties sur le même modèle.

  • [SEQUENCE_CLIENTS] sera utilisée pour générer la clé primaire de la table [CLIENTS] ;
  • [SEQUENCE_MEDECINS] sera utilisée pour générer la clé primaire de la table [MEDECINS] ;
  • [SEQUENCE_CRENEAUX] sera utilisée pour générer la clé primaire de la table [CRENEAUX] ;
  • [SEQUENCE_RVS] sera utilisée pour générer la clé primaire de la table [RVS] ;
  • [SEQUENCE_VERSIONS] sera utilisée pour générer les valeurs des colonnes [VERSIONING] de toutes les tables.

Les triggers

Un trigger est une procédure exécutée par le SGBD avant ou après un événement (Insertion, Modification, Suppression) dans une table. Nous en avons 8 [1] :

Image non disponible

Regardons le code DDL du trigger [TRIGGER_PK_CLIENTS] qui alimente la clé primaire de la table [CLIENTS] :

[TRIGGER_PK_CLIENTS]
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
CREATE TRIGGER "RDVMEDECINS-EF".TRIGGER_PK_CLIENTS
 BEFORE INSERT
 ON "RDVMEDECINS-EF".CLIENTS
REFERENCING NEW AS NEW OLD AS OLD FOR EACH ROW
BEGIN
        SELECT SEQUENCE_CLIENTS.NEXTVAL INTO :new.ID from DUAL;
END;
/
  • lignes 1-5 : avant chaque opération INSERT sur la table [CLIENTS] ;
  • ligne 6 : la colonne [ID] prendra la valeur suivante de la séquence [SEQUENCE_CLIENTS]. La clé primaire aura ainsi des valeurs consécutives fournies par la séquence.

Les triggers [TRIGGER_PK_MEDECINS, TRIGGER_PK_CRENEAUX, TRIGGER_PK_RVS] sont analogues.

Regardons le code DDL du trigger [TRIGGER_VERSIONS_CLIENTS] qui alimente la colonne [VERSIONING] de la table [CLIENTS] :

[TRIGGER_VERSIONS_CLIENTS]
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
CREATE TRIGGER "RDVMEDECINS-EF".TRIGGER_VERSION_CLIENTS
 BEFORE INSERT OR UPDATE
 OF
  VERSIONING
 ON "RDVMEDECINS-EF".CLIENTS
REFERENCING NEW AS NEW OLD AS OLD FOR EACH ROW
BEGIN
        SELECT SEQUENCE_VERSIONS.NEXTVAL INTO :new.VERSIONING from DUAL;
END;
/
  • lignes 1-2 : avant chaque opération INSERT ou UPDATE sur la table [CLIENTS] ;
  • ligne 8 : la colonne [VERSIONING] prendra la valeur suivante de la séquence [SEQUENCE_VERSIONS]. La colonne [VERSIONING] aura ainsi des valeurs consécutives fournies par la séquence.

Les triggers [TRIGGER_VERSION_MEDECINS, TRIGGER_VERSION_CRENEAUX, TRIGGER_VERSION_RVS] sont analogues. Les quatre colonnes [VERSIONING] tirent leur valeurs de la même séquence.

Le script de génération des tables de la base Oracle [RDVMEDECINS-EF] a été placé dans le dossier [RdvMedecins / databases / oracle]. Le lecteur pourra le charger et l'exécuter pour créer ses tables.

Ceci fait, les différents programmes du projet peuvent être exécutés. Ils donnent les mêmes résultats qu'avec SQL Server sauf pour le programme [ModifyDetachedEntities] qui plante pour la même raison qu'il avait planté avec MySQL. On résoud le problème de la même façon. Il suffit de copier le programme [ModifyDetachedEntities] du projet [RdvMedecins-MySQL-01] dans le projet [RdvMedecins-Oracle-01]. On a alors un nouveau problème :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
1-----------------------------
Client [206,x,x,x,616]
2-----------------------------
Client [206,x,x,y,617]

Exception non gérée : System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: Une instruction de mise à jour, d'insertion ou de suppression dans le magasin a affecté un nombre inattendu de lignes (0). Des entités ont peut-être été modifiées ou supprimées depuis leur chargement. Actualisez les entrées ObjectStateManager. ---> System.Data.OptimisticConcurrencyException: Une instruction de mise à jour, d'insertion ou de suppression dans le magasin a affecté un nombre inattendu de lignes (0). Des entités ont peut-être été modifiées ou supprimées depuis leur chargement. Actualisez les entrées ObjectStateManager.
   ...
   à RdvMedecins_01.ModifyDetachedEntities.Main(String[] args) dans d:\data\istia-1213\c#\dvp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\ModifyDetachedE
ntities.cs:ligne 56
  • lignes 1-4 : le client détaché a bien été mis à jour ;
  • ligne 6 : une exception connue. C'est celle qu'on obtient lorsqu'on veut modifier une entité sans avoir la bonne version. Or ici, on ne voulait pas modifier mais supprimer l'entité :
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
      // suppression entité hors contexte
      using (var context = new RdvMedecinsContext())
      {
        // ici, on a un nouveau contexte vide
        // on met client1 dans le contexte dans un état supprimé
        context.Entry(client1).State = EntityState.Deleted;
        // on sauvegarde le contexte
        context.SaveChanges();
}

EF 5 a refusé de supprimer client1 en base, car client1 (ligne 6) n'avait pas la même version. On n'avait pas rencontré ce problème avec MySQL. On s'aperçoit peu à peu que les connecteurs ADO.NET de différents SGBD présentent de légères différences. On corrige comme suit :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
using (var context = new RdvMedecinsContext())
      {
        // ici, on a un nouveau contexte vide
        // on met client1 dans le contexte pour le supprimer
        context.Clients.Remove(context.Clients.Find(client1.Id));
        // on sauvegarde le contexte
        context.SaveChanges();
      }

et ça marche.

V-C. Architecture multicouche s'appuyant sur EF 5

Nous revenons à notre étude de cas décrite au paragraphe , page .

Image non disponible

Nous allons commencer par construire la couche [DAO] d'accès aux données. Pour ce faire, nous dupliquons le projet console VS 2012 [RdvMedecins-SqlServer-02] dans [RdvMedecins-Oracle-02] [1] :

Image non disponible
  • en [2], on supprime le projet [RdvMedecins-SqlServer-02] ;
Image non disponible
  • en [3], on ajoute un projet existant à la solution. On le prend dans le dossier [RdvMedecins-Oracle-02] qui vient d'être créé ;
  • en [4], le nouveau projet porte le nom de celui qui a été supprimé. On va changer son nom ;
Image non disponible
  • en [5], on a changé le nom du projet ;
  • en [6], on modifie certaines de ses propriétés, comme ici le nom de l'assembly ;
  • en [7], le dossier [Models] est supprimé pour être remplacé par le dossier [Models] du projet [RdvMedecins-Oracle-01]. En effet, les deux projets partagent les mêmes modèles.
Image non disponible
  • en [8], les références actuelles du projet ;
  • en [9], on y a ajouté le connecteur ADO.NET d'Oracle avec l'outil NuGet.

Dans le fichier [App.config], on remplace les informations de la base SQL Server par celles de la base Oracle. On les trouve dans le fichier [App.config] du projet [RdvMedecins-Oracle-01] :

[App.config]
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
<!-- chaîne de connexion sur la base -->
  <connectionStrings>
    <add name="monContexte" connectionString="Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User Id=RDVMEDECINS-EF;Password=rdvmedecins;" providerName="Oracle.DataAccess.Client" />
  </connectionStrings>
  <!-- le factory provider -->
  <system.data>
    <DbProviderFactories>
      <remove invariant="Oracle.DataAccess.Client" />
      <add name="Oracle Data Provider for .NET" invariant="Oracle.DataAccess.Client" description="Oracle Data Provider for .NET" type="Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess, Version=4.112.3.0, Culture=neutral, PublicKeyToken=89b483f429c47342" />
    </DbProviderFactories>
  </system.data>

Les objets gérés par Spring changent également. Actuellement on a :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
  <!-- configuration Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
    </objects>
</spring>

La ligne 7 référence l'assembly du projet [ RdvMedecins-SqlServer-02 ]. L'assembly est désormais [ RdvMedecins-Oracle-02 ].

Ceci fait, nous sommes prêts à exécuter le test de la couche [DAO]. Il faut auparavant prendre soin de remplir la base (programme [Fill] du projet [ RdvMedecins-Oracle-01 ]. Le programme de test réussit.

Nous créons la DLL du projet comme il a été fait pour le projet [ RdvMedecins-SqlServer-02 ] et nous rassemblons l'ensemble des DLL du projet dans un dossier [lib] créée dans [ RdvMedecins-Oracle-02 ]. Ce seront les références du projet web [ RdvMedecins-Oracle-03 ] qui va suivre.

Image non disponible

Nous sommes désormais prêts pour construire la couche [ASP.NET] de notre application :

Image non disponible

Nous allons partir du projet [RdvMedecins-SqlServer-03]. Nous dupliquons le dossier de ce projet dans [RdvMedecins-Oracle-03] [1] :

Image non disponible
  • en [2], avec VS 2012 Express pour le web, nous ouvrons la solution du dossier [RdvMedecins-Oracle-03] ;
  • en [3], nous changeons et le nom de la solution et le nom du projet ;
Image non disponible
  • en [4], les références actuelles du projet ;
  • en [5], on les supprime ;
  • en [6], pour les remplacer par des références sur les DLL que nous venons de stocker dans un dossier [lib] du projet [RdvMedecins-Oracle-02].

Il ne nous reste plus qu'à modifier le fichier [Web.config] On remplace son contenu actuel par le contenu du fichier [App.config] du projet [RdvMedecins-Oracle-02]. Ceci fait, on exécute le projet web. Il marche. On n'oubliera pas de remplir la base avant d'exécuter l'application web.


précédentsommairesuivant

  

Licence Creative Commons
Le contenu de cet article est rédigé par Serge Tahé et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2012 Developpez.com.