CM5 Accès aux Bases de Données avec ADO.NET Les objets de connexion Les chaînes de connexion Les schémas Les modes de travail Le mode déconnecté L'objet DataSet Les objets DataColumn et DataRow Les contraintes Les relations Modifications dans un DataSet Insertion, Suppression, Modification 1 Introduction ADO.NET Rappel : Aperçu général de l'architecture.net C# C++ VB Formulaire Windows ADO.NET Common Language Specification (CLS) Librairies de classes de base Common Language Run-time (CLR) Windows de base ASP.NET Formulaire Web Services Web XML 2 Introduction ADO.NET Introduction ADO.NET ADO.NET (ActiveX Data Object for.net) est un outil qui permet l'accès aux données d'une base de données à partir d'une application Il s'applique à n'importe quelle base de données Microsoft (Access, SQL Server) ou autres Oracle, Sybase, MySQL, etc. Il "suffit" de changer la chaîne de connexion pour passer d'un SGBD à un autre ADO.NET peut aussi s'appliquer à d'autres types de données sous forme XML ou Excel SQL Server Express tend à remplacer Access, il a les mêmes caractéristiques que SQL Server mais reste limité à un petit nombre d'utilisateurs et ne dispose pas d'outils liés à l'analyse et à l'optimisation des performances 3 ADO.NET est constitué d'un ensemble de classes qui agissent comme une interface entre : Le programme et la base de données L'objet qui permet une connexion à la base de données est L'objet connexion 4 L'objet de connexion L'objet Connexion permet de spécifier les caractéristiques de la base de données Nom, type (Access, SQL Server, Oracle, ) Dans les 1 ères versions de ADO.NET (1 et 1.1), il existait plusieurs types d'objets connexion selon les bases de données OleConnection (Access) SqlConnection (SQL Server) OracleConnection (Oracle) OdbcConnection (Open Data Base Connectivity) pour ceux qui utilisent la technique ODBC pour accéder aux bases de données 5 L'objet de connexion Ces différentes classes existent toujours, mais ADO.NET a introduit depuis la version 2 Les fabriques de classes pour traiter de façon uniforme les différents types d'objets Les classes OleDbConnection, SQLConnection, OracleConnection, etc. dérivent de la classe DbConnection qui est une classe abstraite. Pour lire/écrire dans une base de données, il faut donc initialiser un objet de connexion En spécifiant une chaîne de connexion En initialisant les propriétés de l'objet 6 1
L'objet de connexion L'espace de nom requis est using System.Data.Common; Tous les objets de connexion (Ole, SQL, Oracle) possèdent Un constructeur qui admet en paramètre la chaîne de connexion Des propriétés comme ConnectionString : chaîne de connexion Database : nom de la base de données DataSource : nom de la source de données (à utiliser pour Access) Password : mot de passe d'accès à la base de données State : état de la connexion Etc. 7 L'objet de connexion La classe DbConnexion possèdent des méthodes : Ouvrir ou fermer une connexion Open() pour une ouverture explicite de connexion Close() pour une fermeture Créer des objets de types DbCommand CreateCommand pour permettre l interrogation de la base de donner Connaître le schéma de la base de données GetSchema() retourne des informations sous forme d un DataTable Etc. Avec des exceptions possibles (DbException) 8 Les chaînes de connexion Elles contiennent les informations de connexion contenues dans les objets qui implémentent de type DbConnection Propriété «ConnectionString» Elles sont décrites sous forme de couples "mot-clé=valeur" User id = ; Password= ; Data Source = ; Pour les salles de TP, pour une connexion Oracle : OracleConnection con = new OracleConnection(); Pour une connexion dans les salles de TP (en interne) ConnectionString= "User Id=toto; Password=secret; Data Source=//eluard:1521/ense2015" Pour une connexion dans les salles depuis l extérieur ConnectionString= "User Id=toto; Password=secret; Data Source=//ufrsciencestech.u-bourgogne.fr:25559/ense2015" 9 Les fabriques de classes Les fabriques de classes visent à permettre de programmer de façon générique pour tous les types de bases de données et donc les objets de connexion La classe DbProviderFactories contient une méthode statique GetFactories() qui renvoie les informations sur les providers susceptibles d'être utilisés sur une machine Sous forme d'un objet de type DataTable (dont le contenu peut facilement être visualisé dans un DataGridView). 10 Les fabriques de classes Chaque ligne du DataTable comporte 5 colonnes Name, Description, InvariantName (string) System.Data.Odbc, System.Data.OleDb, System.Data.OracleClient, System.Data.SQLClient AssemblyQualifiedName, SupportedClasses La classe «DbProviderFactories» comporte également une méthode «static» : GetFactory(String provider) qui retourne une instance de la classe «DbProviderFactory» pour le fournisseur spécifié Les fabriques de classes Création d'une fabrique pour un accès à une base de données Oracle OracleConnection = new OracleConnection(); Con.ConnectionString= ; DbProviderFactory dbpf; dbpf=dbproviderfactories.getfactory("oracle.dataaccess.client"); Ici, «dbpf» est une instance de la «OracleClientFactory» qui dérive et implémente la classe DbProviderFactory pour le fournisseur Oracle 11 12 2
Les schémas de la base de données La méthode GetSchema de la classe DbConnection, permet d'obtenir Des informations : noms des tables, types des colonnes, etc. Sur une base de données ouverte Le résultat est donné sous forme d'un DataTable qui comporte 3 colonnes La 1 ère colonne indique un nom de collection qui peut être utilisé pour avoir davantage d'informations sur cette collection (aussi avec la méthode GetSchema) "Tables", "Datatypes", "Columns", "Indexes", "Procedures", "DataSources" 13 Les schémas de la base de données Affichage des noms des tables d'une base de données DataTable dt=conn.getschema("tables"); for (int i=0; i<dt.rows.count; i++) if (dt.rows[i]["table_type"].tostring() == "TABLE" ) //table utilisateur, pas système Edition.Text+=dt.Rows[i]["TABLE_NAME"].ToString(); Le DataTable comporte une colonne TABLE_TYPE qui vaut TABLE pour une table utilisateur, TABLE_NAME est la colonne du nom de la table Edition est une zone d'édition (TextBox) 14 Les modes de travail Il existe deux modes de travail: Connecté et déconnecté En mode connecté, le client ouvre une connexion et reste en communication avec la base. Il ne reçoit pas tout le résultat d'un SELECT en une fois, mais ligne par ligne Il les réclame une à une En mode déconnecté, le client effectue une requête SELECT, sans avoir à ouvrir explicitement une connexion. La connexion est dite logiquement coupée La connexion est ouverte, le SELECT est exécuté, le résultat est stocké dans une sorte de tableau en mémoire, puis la connexion est fermée. 15 Le mode déconnecté Dans ce mode, le client exécute un SELECT et reçoit en bloc le résultat dans un objet de type DataSet. La méthode Fill (de la classe DbDataAdapter) Ouvre la connexion, Remplit le DataSet (en paramètre) avec le résultat du SELECT Ferme la connexion Les données du DataSet peuvent être modifiées, et la mise à jour de la base avec le DataSet modifié doit être explicitement demandée par la méthode Update. 16 Le mode déconnecté En mode déconnecté, les objets suivants sont utilisés : L'objet de connexion (DbConnection) (même si on n'ouvre pas explicitement la connexion) L'objet d'adaptation (DbDataAdapter), dans lequel on spécifie la commande SELECT, mais également les commandes d'ajout, suppression, modification L'objet DataSet qui comporte le résultats d une commande SELECT et qui peut représenter une portion de la base de données. 17 Les objets d'adaptation de données L'objet d'adaptation joue le rôle d'interface entre la base de données et l'objet DataSet qui va contenir le résultat des requêtes SELECT. La classe DbDataAdapter est une classe abstraite qui est la classe de base pour les classes OleDbDataAdapter, SQLDataAdapter, OracleDataAdapter, pour les différents types de bases de données 18 3
Les objets d'adaptation de données La méthode Fill de cette classe permet de remplir un objet de type DataSet Avec le résultat d'une requête SELECT Cette méthode comporte plusieurs surcharges int Fill(DataSet) qui remplit le DataSet, une table est ajoutée au DataSet int Fill(DataSet, string nomtable) qui permet en plus de spécifier un nom de table (pas nécessairement existante, pour le stocker le résultat), une nouvelle table est créée. int Fill(DataSet, startrecord, nbrecord, nomtable) qui permet de sélectionner un sous-ensemble des enregistrements résultats int Fill(DataTable) qui insère directement le résultat du SELECT dans un objet de type DataTable 19 Les objets d'adaptation de données Exemple d'utilisation pour Oracle OracleDbConnection conn; DbProviderFactory dbpf; DbDataAdpater dba; // initialisation de la connexion conn et de dbpf dba=dbpf.createdataadapter(); dba.selectcommand=conn.createcommand(); dba.selectcommand.commandtext="select * FROM personne"; DataSet ds=new DataSet(); dba.fill(ds,"personne"); 20 Les objets d'adaptation de données Les données qui sont stockées dans le DataSet, peuvent être modifiées, supprimées ou complétées (ajout) La mise à jour de la base peut alors être faite avec la méthode Update de la classe DbDataAdapter (pour le DataTable concerné) Qui exécute les commandes définies dans les propriétés UpdateCommand, InsertCommand, et DeleteCommand Commandes à exécuter 21 L'objet DataSet Il contient les données provenant d'un ou plusieurs SELECT On peut comparer le DataSet à un extrait de la base de données, en mémoire et chez le client (dans l'application) Mais un DataSet peut être composé de tables, qui ne sont pas forcément directement «identiques» à celles de la base de données Résultats de commandes select 22 L'objet DataSet La classe DataSet possède Un constructeur qui permet de fixer le nom du DataSet, et Des propriétés comme Relations : qui est une collection de relations (objets de type DataRelation) Tables : qui est une collection de tables (objets de type DataTable) DataTableCollection HasErrors (T/F) qui indique si une modification a provoqué une erreur d'intégrité dans l'une des tables au moins. 23 L'objet DataSet Exemple, si on suppose qu'un DataSet (ds) comporte une table "Personne" avec les colonnes Nom et Prénom, et qu'il s'agit de la 1 ère table insérée dans ce DataSet. La table peut être référencée par : ds.tables[0] ds.tables["personne"] La 1 ère est plus rapide mais plus sujette à erreur. Chaque élément d'un DataSet est un objet de type DataTable qui est bien adpaté à un affichage avec un DataGridView, en utilisant l'attribut DataSource du DataGridView 24s 4
L'objet DataTable Les objets DataColumn et DataRow Un DataTable possède deux propriétés qui concernent les données de la table Columns qui est une collection d'objets de type DataColumn, qui donne des informations sur une colonne Rows qui est une collection d'objets de type DataRow qui donne accès au contenu des lignes. Exemple : informations sur les colonnes foreach (DataColumn dc in ds.tables[0].columns) Edition+=dc.ColumnName +" "+ dc.columntype.name; Ce qui donnerait Nom String Prénom String 25 L'objet DataColumn fournit des informations sur une colonne Il possède des propriétés comme AllowDbNull (T/F): qui indique des valeurs "null" peuvent être insérées dans la colonne AutoIncrement (T/F) : qui indique s'il s'agit d'un champ autoincrémenté AutoIncrementSeed (valeur de départ), AutoIncrementStep (pas) ColumnName, ColumnType DefaultValue, ReadOnly, Unique, MaxLength (texte) Ordinal (position de la colonne) Etc. 26 Les objets DataColumn et DataRow L'objet DataRow donne les informations sur une ligne de la table. Il comporte des propriétés comme : ItemArray : tableau d'objets (Object []) des contenus des colonnes pour la ligne Table : DataTable auquel appartient la ligne RowState : état de la ligne, de type énuméré DataRowState (Detached, Unchanged, New, Deleted ou Modified) HasErrors (T/F) : indique si une modification a provoqué une erreur pour la ligne Etc. 27 Les objets DataColumn et DataRow Accès au contenu d'une ligne (DataSet ds) for (int i=0; i< ds.tables[0].rows.count; i++) { string n = (string) ds.tables[0].rows[i]["nom"]; string p = (string) ds.tables[0].rows[i]["prénom"]; Edition+= n +" "+ p + "\n"; } Si un champ a une valeur nulle, il est possible d'utiliser la valeur DBNull pour tester, ou d'avoir recours aux types "nullable" 28 Les contraintes / clé primaire Lors de la création d'une table d'un DataSet, il est possible de spécifier des contraintes Clé(s) primaire(s) et clé(s) étrangère(s) Unicité Une clé primaire peut être composée de plusieurs colonnes Par exemple : nom et prénom Elle est donc formée d'un tableau de colonnes DataColumn [] Exemple pour la table "Personne" (avec dt de type DataTable) DataColumn[ ] cols= {dt.columns["nom"], dt.columns["prenom"]} dt.primarykey = cols; 29 Les contraintes / unicité De la même façon, on peut indiquer une contrainte d'unicité sur une colonne, Les autres contraintes comme l'unicité sont décrites dans la propriété Constraints du DataTable Qui comporte une collection de contraintes (ConstraintCollection), i.e. d'objets de type UniqueConstraint ou ForeignKeyConstraint Qui dérivent de la classe Constraint Exemple : Unicité du n d'insee pour une personne ds.tables["personne"].constraints.add(new UniqueConstraint(ds.Tables["Personne"].Columns["INSEE"])); 30 5
Les contraintes / clé étrangère Un autre type de contrainte que l'on peut exprimer concerne les clés étrangères Une clé étrangère peut être construite par : ForeignKeyConstraint(DataColumn, DataColumn) Pour des clés "simples" ForeignKeyConstraint(DataColumn [ ], DataColumn [ ]) Pour des clés composées de plusieurs colonnes Les objets de type ForeignKeyConstraint ont des propriétés qui décrivent les actions à effectuer quand une ligne est mise à jour (Cascade, None, SetDefault, SetNull) une ligne est supprimée (Cascade, None, SetDefault, SetNull) et en cas d'appel à AcceptChanges (Cascade, None) 31 Les contraintes / clé étrangère Exemple : On suppose qu'une personne un attribut NoContrat, qui est une clé étrangère, et clé primaire d'une table "Assurance" ds.tables["personne"].constraints.add ( new ForeignKeyConstraint( ds.tables["assurance"].columns["numcontrat"], ds.tables["personne"].columns["nocontrat] ) ); 32 Les relations Les relations Les relations permettent de définir des relations entre les champs de deux tables Les relations sont décrites par la propriété Relations du DataSet, Qui est de type DataRelationCollection Un objet de type DataRelation est construit en spécifiant Un nom pour la relation, Un ou plusieurs noms de la table "mère" Un ou plusieurs noms de la table "fille" Un booléen qui indique s'il s'agit d'une contrainte de type clé étrangère. 33 Exemple de 2 tables Personne(IDPers, Nom, Prénom, Adresse) Assurance(IDAss, IDP, Contrat, Date) On peut établir une relation entre la colonne IDPers de Personne, et IDP de Assurance DataRelation dr= new DataRelation("PersAss", ds.tables["personne"].columns["idpers"], ds.tables["assurance"].columns["idp"]); ds.relations.add(dr); 34 Les relations Modification dans un DataSet Pour accéder à tous les contrats d'assurance d'une personne, on peut utiliser les relations qui ont été définies foreach (DataRow dr in ds.tables["personne"].rows.getchildrows( "PersAss")) Edition+=dr[IDAss]; Edition = zone d'édition (RichTextBox) 35 Des modifications peuvent être faites dans le DataSet, mais elles doivent être ensuite répercutées dans la base de données On considère par exemple la table Peronne(IDPers, Nom, Prénom, DateN) Avec IDPers numérique, clé primaire Nom, Prénom de type chaîne de caractères DateN de type date 36 6
Modification dans un DataSet Le remplissage a été effectué (DataDbAdapter db, DataSet ds) db.fill(ds, "Personne"); Modification du nom de la i ème personne ds.tables["personne"].rows[i]["nom"]="dupont"; Suppression de la j ème ligne ds.tables["personne"].rows[j].delete(); Insertion d'une ligne DataRow dr =ds.tables["personne"].newrow(); dr["nom"]= "Durant";. ds.tables["personne"].rows.add(dr); 37 Modification dans un DataSet Il est possible de connaître l'état d'une ligne grâce à la propriété RowState (de type DataRowState) Unchanged, Added, Deleted, Modified Pour forcer la mise à jour de la base de données, il faut exécuter la méthode Update à l'objet DbDataAdpater (db) db.update(ds.tables["personne"]); ADO.NET parcourt alors la liste des lignes et fait exécuter les commandes SQL contenues dans les objets UpdateCommand, DeleteCommand et InsertCommand. 38 Modification dans un DataSet Il faut donc créer ces objets Soit de façon automatique (si la table a une clé primaire) mais code généré pas très optimisé Soit de façon manuelle (si pas de clé primaire, ou pour un code plus "léger") Génération automatique si la table comporte une clé primaire (DbProviderFactory dbpf, DbDataAdapter db) DbCommandBuilder cmdb=dbpf.createcommandbuilder(); cmdb.dataadapter=db; db.updatecommand=cmdb.getupdatecommand(); db.insertcommand=cmdb.getinsertcommand(); db.deletecommand=cmdb.getdeletecommand(); Conclusion ADO.NET offre un ensemble de classes permettant de gérer des données en base. Il existe deux modes de travail : connecté et déconnecté (comme présenté), Il y a également des classes dédiées à la gestion des procédures stockées (PLSQL) ADO.NET permet de gérer différentes bases de données de façon "quasi" uniforme Avec cependant quelques limites (mises à jour). 39 40 7