.jpg)
L’article ci-dessous est la traduction de l’article de Scott Guthrie.
Par Fabien Lavocat, International Program Manager Visual Studio, formateur au laboratoire .NET à l’école supérieure d’informatique SUPINFO.
Note de l’auteur : Cet article est rédigé pour Visual Studio 2008 Bêta 2 - .NET Framework 3.5 Bêta 2. [Version RTM] Les lignes en rouge comme celles-ci, correspondent aux mises à jour pour la version RTM de Visual Studio 2008 et du .NET Framework 3.5.
Au cours des dernières semaines j'ai écrit une série d’articles sur LINQ to SQL. LINQ to SQL est un O/RM intégré (object relational mapper) qui est fourni dans le .NET Framework 3.5, et qui vous permet de modéliser facilement des bases de données relationnelles à l'aide des classes .NET. Vous pouvez utiliser des expressions LINQ pour interroger la base de données, ainsi que mettre à jour / Insérer / Supprimer des données.
Voici les trois premières parties de ma série d’articles sur LINQ to SQL :
Dans l’article d’aujourd'hui, j'aborderai comment nous pouvons utiliser le modèle de données créé précédemment et l’utiliser pour mettre à jour, insérer et supprimer des données. Je vais également expliquer comment intégrer correctement des règles d'entreprise et une logique de validation personnalisée avec notre modèle de données.
Dans la Partie 2 de cette série, j’ai montré comment créer les classes du modèle de données LINQ to SQL en utilisant le designer LINQ to SQL intégré à VS2008. Ci-dessous, vous pouvez voir le modèle de données créé pour la base de données d’exemple Northwind que nous utilisons dans cet article :
.jpg)
Lorsque nous avons conçu notre modèle de données en utilisant le designer de données LINQ to SQL ci-dessus nous avions défini cinq classes du modèle de données : Product, Category, Customer, Order et OrderDetail. Les propriétés de chaque classe correspondent aux colonnes de la table correspondante dans la base de données. Chaque instance d'une entité de classe représente une ligne de la table de la base de données.
Lorsque nous avons défini notre modèle de données, le concepteur LINQ to SQL a également créé une classe DataContext personnalisée qui fournit le canal principal par lequel nous allons interroger notre base de données et appliquer des mises à jour/modifications. Dans l’exemple de modèle de données que nous avons défini au-dessus, cette classe a été nommée « NorthwindDataContext ». La classe NorthwindDataContext possède des propriétés qui représentent chaque table que nous avons modélisé dans la base de données (en particulier : Products, Categories, Customers, Orders, OrderDetails).
Comme je l’ai dit dans la partie 3 de cette série d’articles, nous pouvons facilement employer des expressions avec la syntaxe LINQ pour interroger et récupérer des données de notre base de données utilisant la classe NorthwindDataContext. LINQ to SQL traduira alors automatiquement les expressions de requêtes de LINQ en code SQL approprié à l’exécution.
Par exemple, je pourrais écrire l'expression LINQ ci-dessous pour rechercher un seul objet Product en recherchant par nom du produit :
.jpg)
Je pourrais alors écrire l'expression LINQ ci-dessous pour rechercher tous les produits de la base de données qui n'ont pas encore eu une commande passée, et qui coûtent plus de $100 :
.jpg)
Notez ci-dessus comment j'utilise l'association « OrderDetails » pour chaque produit dans le cadre de la requête pour extraire uniquement les produits qui n'ont jamais été commandés.
Lorsque nous envoyons des requêtes et récupérons des objets tels que les instances de produits comme ci-dessus, LINQ to SQL va, par défaut, suivre toutes les modifications et toutes les mises à jour que nous ferons ultérieurement à ces objets. Nous pouvons effectuer autant de requêtes que nous le souhaitons et les modifier tout ce que l’on veut à l'aide du DataContext de LINQ to SQL et ces modifications seront toutes suivies ensemble.
Remarque : le suivi des modifications de LINQ to SQL se produit du côté du client - et non dans la base de données. Cela signifie que vous ne consommez pas de ressources de base de données lorsque l'utilisez, vous ne devez ni modifier ni installer quoi que ce soit dans la base de données pour l'activer.
Après avoir apporté des modifications que nous souhaitons sur les objets que nous avons extrait grâce à LINQ to SQL, nous pouvons ensuite éventuellement appeler la méthode « SubmitChanges() » de notre DataContext pour appliquer les modifications dans la base de données. Cela entraînera de la part de LINQ to SQL, un calcul dynamique et l’exécution d’un code SQL approprié pour mettre à jour la base de données.
Par exemple, je peux écrire le code ci-dessous pour mettre à jour les prix et le nombre d'unités en stock du produit « Chai » dans la base de données :
.jpg)
Lorsque j'appele la méthode Northwind.SubmitChanges() ci-dessus, LINQ to SQL va dynamiquement construire et exécuter une instruction SQL « UPDATE » qui permet de mettre à jour les valeurs des propriétés de deux produits que nous avons modifiés ci-dessus.
Je peux ensuite écrire le code ci-dessous pour exécuter une boucle sur les produits non populaires et chers et définir la valeur de la propriété « ReorderLevel » (Niveau de réapprovisionnement) à zéro :
.jpg)
Lorsque j'appelle la méthode Northwind.SubmitChanges() ci-dessus, LINQ to SQL calcule et exécute un ensemble approprié d'instructions UPDATE pour modifier les produits qui avaient leur propriété « ReorderLevel » modifiée.
Notez que si des valeurs de propriétés d’un Product n'ont pas été modifiées par le code ci-dessus, l’objet ne sera pas considéré comme modifié et LINQ to SQL par conséquent n’exécutera pas une mise à jour de ce produit dans la base de données. Par exemple, si le UnitPrice (Prix unitaire) du produit « Chai » était déjà à $2 et le nombre d'unités en stock est de 4, puis en appeler la méthode SubmitChanges() ne provoquera pas de mise à jour dans la base de données Instructions. De même, seuls les produits dans le deuxième exemple dont ReorderLevel n'est pas déjà à 0 seront actualisés lorsque la méthode SubmitChanges() sera appelée.
En plus de mettre à jour des enregistrements existants dans la base de données, LINQ to SQL permet évidemment d'insérer et de supprimer des données. Vous pouvez accomplir ceci en s'ajoutant/enlevant des éléments de données de la collection de tables du DataContext, et en appelant ensuite la méthode SubmitChanges(). LINQ to SQL suivra les ajouts/suppressions, et exécutera automatiquement les requêtes SQL INSERT ou DELETE appropriées quand la méthode SubmitChanges() sera appelés.
Insérer un nouveau Product
Je peux ajouter un nouveau produit à ma base de données en créant une nouvelle instance de la classe « Product », en définissant ses propriétés, et ensuite en l’ajoutant à ma collection « Products » du DataContext :
.jpg)
[Version RTM] Dans la version RTM, la méthode Add() de l’interface ITable est renommée en InsertOnSubmit().
Remplacez donc la ligne « northwind.Products.Add(myProduct); » par la ligne « northwind.Products.InsertOnSubmit(myProduct); »
Quand nous appelons la méthode SubmitChanges() ci-dessus, un nouvel enregistrement est créer dans notre table des produits.
Supprimer des produits
Tout comme je peux dire que je veux ajouter un nouveau produit à la base de données en ajoutant un objet de type Product dans la collection de produits du DataContext, je peux également dire que je veux supprimer un produit de la base de données en l'enlevant de la collection des produits du DataContext :
.jpg)
[Version RTM] Dans la version RTM, la méthode RemoveAll() de l’interface ITable est renommée par DeleteAllOnSubmit().
Remplacez donc la ligne « northwind.Products.RemoveAll(lameProducts); » par « northwind.Products.DeleteAllOnSubmit(lameProducts); »
PS : La méthode Remove() de l’interface ITable est renommée en DeleteOnSubmit().
Notez ci-dessus comment je recherche une séquence de produits non contigües que personne n'a jamais commandés utilisant une requête LINQ, et ensuite en la passant à la méthode RemoveAll() sur ma collection « Products » du DataContext. Quand nous appelons la méthode « SubmitChanges() », ci-dessus, tous les enregistrements Product seront supprimés de notre table de produits.
Ce qui rend les mappeurs O/R comme LINQ to SQL extrêmement flexible est qu'ils nous permettent de modéliser facilement les relations entre les tables dans notre modèle de données. Par exemple, je peux modéliser chaque Product (Produit) dans une Category (Catégorie), chaque Order (Commande) qui contient des OrderDetails (Détail de la commande) pour les articles, associer chaque ligne OrderDetail avec un produit et avoir pour chaque client, un jeu de commandes associées. J'ai déjà abordé comment créer et modéliser ces relations dans la deuxième partie de cette série d’articles.
LINQ to SQL me permet de tirer parti de ces relations pour à la fois interroger et mettre à jour mes données. Par exemple, je peux écrire le code ci-dessous pour créer un nouveau produit et l’associer à la catégorie « Beverages (Boissons) » existante dans ma base de données comme suit :
.jpg)
[Version RTM] Dans la version RTM, la méthode Add() de l’interface ITable est renommée en InsertOnSubmit().
Remplacez donc la ligne « northwind.Products.Add(myProduct); » par la ligne « northwind.Products.InsertOnSubmit(myProduct); »
Notez au-dessus de la façon dont j'ajoute l'objet Product dans la collection Products de la Category. Cela indique qu'il y a une relation entre les deux objets, et donc LINQ to SQL maintiendra automatiquement la relation de clé étrangère / clé primaire entre les deux lorsque j'appele la méthode « SubmitChanges() ».
Un autre exemple pour montrer comment LINQ to SQL peut aider à gérer lui-même les relations entre les tables et nous aide à nettoyer notre code. Examinons l’exemple ci-dessous où je créé une nouvelle commande pour un client existant. Après avoir défini la date et les coûts de livraison de la commande, j’ajoute ensuite deux produits à la commande. Ensuite, j’associe la commande avec le client et enfin je mets à jour la base de données avec toutes les modifications.
.jpg)
[Version RTM] Dans la version RTM, la méthode Add() est renommée en InsertOnSubmit().
Remplacez donc la ligne « northwind.Products.Add(myProduct); » par la ligne « northwind.Products.InsertOnSubmit(myProduct); »
Comme vous pouvez le constater, le modèle de programmation pour effectuer tout ce travail est extrêmement propre orienté objet.
Une transaction est un service fourni par une base de données (ou un autre gestionnaire de ressources) afin de garantir qu'une série d'actions individuelles se produit atomiquement - ce qui signifie que soit toutes réussissent soit aucune, et sont alors toutes automatiquement annulées avant que quelque chose puisse se produire.
Lorsque vous appelez la méthode SubmitChanges() de votre DataContext, les mises à jour sont toujours encapsulées dans une transaction. Cela signifie que votre base de données ne sera pas dans un état incohérent si vous effectuez plusieurs modifications – soit toutes les modifications effectuées sur votre DataContext sont enregistrés soit aucune.
Si aucune transaction n'est déjà démarrée, l’objet DataContext de LINQ to SQL va lancer automatiquement une transaction de base de données pour protéger les mises à jour lorsque vous appelez SubmitChanges(). Où bien, LINQ to SQL vous permet également de définir explicitement et d’utiliser votre propre objet TransactionScope (fonctionnalité introduite dans .NET 2.0). Cela facilite l'intégration du code LINQ to SQL avec le code d'accès aux données existant que vous disposez. Cela signifie également que vous pouvez incorporer des ressources non présentes en base de données, dans la transaction - par exemple : vous pourriez envoyer un message MSMQ, mettre à jour le système de fichiers (à l'aide de la nouvelle prise en charge du système de fichiers transactionnelles), etc. - et utiliser tous ces éléments dans une même transaction que vous utilisez pour mettre à jour de votre base de données avec LINQ to SQL.
Un des choses les plus importantes que les développeurs doivent penser au moment de travailler avec des données est comment intégrer la validation et les règles logique du métier. C’est grâce à la prise en charge de LINQ to SQL de différentes façons, que les développeurs intègrent proprement celle-ci avec leurs modèles de données.
LINQ to SQL vous permet d'ajouter cette logique de validation une seule fois - et de la faire alors honorer indépendamment d'où/comment le modèle de données que vous avez créé est utilisé. Ceci vous évite de répéter la logique dans de multiples endroits, et de mener à un modèle de données beaucoup plus maintenable et plus propre.
Quand vous définissez vos classes du modèle de données en utilisant le concepteur LINQ to SQL dans Visual Studio 2008, elles seront par défaut annoté avec quelques règles de validation impliquées dans le schéma des tables de la base de données.
Les types de données des propriétés dans les classes du modèle de données correspondront avec les types de données du schéma de la base de données. Ceci signifie que vous obtiendrez des erreurs de compilation si vous essayez d'assigner un booléen à une valeur décimale, ou si vous essayez de convertir implicitement les types numériques incorrectement.
Si une colonne de la base de données est marquée comme Nullable, la propriété correspondante dans la classe du modèle de données créé par le concepteur LINQ to SQL sera un type Nullable. Les colonnes qui ne sont pas marquées comme Nullable déclencheront automatiquement une exception si vous essayer de lui affecter une valeur nulle. LINQ to SQL vérifiera que les valeurs des colonnes identifiant/unique sont correctement remplies.
Vous pouvez évidemment utiliser le concepteur LINQ to SQL pour substituer le schéma par défaut conduit par la validation des paramètres si vous le souhaitez - mais par défaut vous les avez automatiquement et ne devez prendre aucune mesure additionnelle pour le permettre. LINQ to SQL gère également automatiquement l’échappement des valeurs SQL pour vous - ainsi vous n'avez pas besoin de s'inquiéter des attaques par injection SQL en l'employant.
La validation de type de données conduite par le schéma est utile dans un premier temps, mais ne l’est habituellement pas assez pour des scénarios réels.
Considérez par exemple un scénario avec notre base de données Northwind où nous avons une propriété « téléphone (Phone) » pour la classe « client (Customer) » qui est définie dans la base de données en tant que nvarchar. Les développeurs utilisant LINQ to SQL pourraient écrire le code ci-dessous pour le mettre à jour utilisant un numéro de téléphone valide :
.jpg)
Le défi que nous courrons avec notre application, cependant, est que le code ci-dessous est également légal d'une perspective pure de schéma SQL (parce que c'est toujours une chaîne quoique ce ne soit pas un numéro de téléphone valide) :
.jpg)
Pour empêcher de faux numéros de téléphone d'être ajouté dans notre base de données, nous pouvons ajouter une règle de validation de propriété personnalisée à notre classe Customer du modèle de données. Ajouter une règle pour valider des numéros de téléphone utilisant ce dispositif est vraiment facile. Tout ce dont on a besoin de faire est d'ajouter une nouvelle classe partielle à notre projet qui définit la méthode ci-dessous :
.jpg)
Le code ci-dessus prend les avantages des deux caractéristiques de LINQ to SQL :
Dans mon exemple de validation ci-dessus, j’utilise la méthode partielle OnPhoneChanging qui est exécutée quand quelqu'un place par programmation, la propriété « Phone » sur un objet Customer. Cependant, je peux utiliser cette méthode pour valider l'entrée (dans ce cas-ci j’utilise une expression régulière). Si tout se passe avec succès, je sors de la méthode et LINQ to SQL supposera que la valeur est valide. S'il y a des problèmes avec la valeur, je peux lever une exception dans la méthode de validation – ce qui empêchera la tâche d'avoir lieu.
La validation de niveau de propriété comme utilisé dans le scénario ci-dessus est très utile pour valider différentes propriétés dans une classe du modèle de données. Parfois, bien que, vous vouliez ou aillez besoin de valider entre elles des valeurs de plusieurs propriétés sur un objet.
Considérez par exemple un scénario avec un objet Order où vous définissez les propriétés « OrderDate » et « RequiredDate » :
.jpg)
[Version RTM] Dans la version RTM, la méthode Add() est renommée en InsertOnSubmit().
Remplacez donc la ligne « northwind.Products.Add(myProduct); » par la ligne « northwind.Products.InsertOnSubmit(myProduct); »
Le code ci-dessus est légal d'une perspective pure de base de données SQL – même s’il ne semble absolument pas raisonnable pour la date de livraison exigée soit la date d’hier.
La bonne nouvelle est que LINQ to SQL en Beta2 rend facile pour nous d'ajouter des règles de validation de niveau des entités pour se protéger des erreurs qui peuvent survenir. Nous pouvons ajouter une classe partielle pour notre entité « Order » et implémenter la méthode partielle OnValidate() qui sera appelée avant que les valeurs de l'entité soient persistées dans la base de données. Avec cette méthode de validation nous pouvons alors accéder et valider toutes les propriétés de classe du modèle de données :
.jpg)
Dans cette méthode de validation je peux vérifier chacune des valeurs des propriétés de l’entité (et même obtenir un accès en lecture seule à ses objets associés), et lève une exception comme nécessaire si les valeurs sont incorrectes. Toutes les exceptions levées par la méthode OnValidate() arrêteront n'importe quelle modification dans la base de données, et fera un rollback de tous les changements de la transaction.
Il y a des périodes où vous voulez ajouter une logique de validation spécifique pour insérer, mettre à jour ou supprimer des scénarios. LINQ to SQL en Beta2 le permet en ajoutant une classe partielle pour étendre votre classe DataContext et puis en implémentant des méthodes partielles pour personnaliser la logique d'insertion, de mise à jour et de suppression pour vos entités du modèle de données. Ces méthodes seront appelées automatiquement quand vous appellerez la méthode SubmitChanges() de votre DataContext.
Vous pouvez ajouter une logique de validation appropriée dans ces méthodes – et si elle passe, demandez à LINQ to SQL de continuer de persister les changements appropriés dans la base de données (en appelant la méthode de « ExecuteDynamicXYZ » du DataContext) :
.jpg)
Qu'est-ce qui est bien sur l'ajout de méthodes ci-dessus est que la méthode correspondante est automatiquement appelé indépendamment de la logique de scénario qui ont provoqués la création, mise à jour et la suppression des objets de données. Par exemple, imaginez un scénario simple dans lequel nous créons une nouvelle commande (Order) et l'assotions à un client existant :
.jpg)
[Version RTM] Dans la version RTM, la méthode Add() est renommée en InsertOnSubmit().
Remplacez donc la ligne « northwind.Products.Add(myProduct); » par la ligne « northwind.Products.InsertOnSubmit(myProduct); »
Lorsque nous appelons la méthode Northwind.SubmitChanges() ci-dessus, LINQ to SQL déterminera ce dont elle a besoin pour persister une nouvelle commande (Order) et que notre méthode partielle « InsertOrder » soit automatiquement appelée.
Il y a des périodes où quand on ajoute la logique de validation celle-ci ne peut pas être fait simplement en regardant les différentes opérations d'insertion/mise à jour/suppression – et à la place vous voulez pouvoir regarder la liste entière des changements des opérations qui se produisent pour une transaction.
Commençant avec la Bêta2 de .NET 3.5, LINQ to SQL vous permet maintenant d'obtenir l'accès à cette liste de changement en appelant la méthode publique DataContext.GetChangeList(). Celle-ci renverra un objet de type ChangeList qui expose des collections de chaque addition, suppression et modification qui aura été apportée.
Une approche que vous pouvez éventuellement utiliser pour les scénarios avancés est à la sous-classe la classe DataContext et substituer sa méthode SubmitChange(). Vous pouvez alors rechercher la méthode ChangeList() pour l'opération de mise à jour et traiter n'importe quelle validation personnalisée que vous voulez avant de l'exécuter :
.jpg)
Le scénario ci-dessus est légèrement avancé - mais c’est bien de savoir que vous avez toujours la capacité à dérouler et tirer profit de lui si nécessaire.
Une des choses auxquelles les développeurs ont besoin de penser à propos des systèmes de base de données multi utilisateurs est comment manipuler les mises à jour simultanées de mêmes données dans la base de données. Par exemple, supposez que deux utilisateurs recherchent un objet produit dans une application, et un des utilisateurs change le ReorderLevel en 0 tandis que l'autre le change en 1. Si les deux utilisateurs essayent alors d’enregistrer le produit de nouveau à la base de données, le développeur doit décider comment manipuler les conflits de changement.
Une approche consiste à juste « laisser le dernier auteur gagner » - ce qui veut dire que la première valeur soumise par l’utilisateur sera perdue sans que les utilisateurs le sachent. Ceci est généralement considéré une expérience d'application pauvre (et incorrecte).
Une autre approche que LINQ to SQL prend en charge est d'employer un modèle optimiste concurrentiel - où LINQ to SQL détectera automatiquement si les valeurs originales dans la base de données ont été mises à jour par quelqu'un d'autre avant que les nouvelles valeurs soient persistées. LINQ to SQL peut alors fournir une liste de conflit de valeurs changées au développeur et lui permettre de réconcilier les différences ou fournissez à l'utilisateur de l'application une indication avec l’interface utilisateur pour lui demander ce qu’ils veulent faire.
Je couvrirai comment employer les accès concurrentiels optimiste avec LINQ to SQL dans un futur article.
Une des questions que les développeurs (et particulièrement les administrateurs de base de données) qui doivent écrire des procédures stockées avec du code SQL personnalisé se posent généralement lorsque qu’ils voient LINQ to SQL pour la première fois est à « mais comment puis-je avoir contrôle complet du code SQL qui est exécuté ? »
La bonne nouvelle est que LINQ to SQL possède un modèle très flexible qui permet aux développeurs de remplacer les instructions SQL dynamiques exécutées automatiquement par LINQ to SQL et au lieu d’appeler des procédures stockées personnalisés pour insérer, mettre à jour ou supprimer des données, ils (ou un administrateur de base de données) les définissent eux-mêmes.
Ce qui est très intéressant, c’est de que vous pouvez commencer par définir votre modèle de données et vous aurez LINQ to SQL qui, automatiquement, gérera la logique SQL d'insertion, de mise à jour et de suppression pour vous. Vous pouvez ensuite ultérieurement personnaliser le modèle de données en utilisant vos propres procédures stockées personnalisé ou SQL pour les mises à jour - sans avoir à modifier la logique de votre application qui utilise votre modèle de données, ni modifier la logique des règles de validation ou de travail qu’il prend en charge (tout cela reste la même). Cela permet une grande flexibilité pour créer votre application.
J'aborderai la personnalisation de vos modèles de données pour utiliser des procédures stockées ou du code SQL personnalisé dans prochain article.
J’espère que l’article ci-dessus fournit un bon résumé de la façon dont vous pouvez facilement utiliser LINQ to SQL pour mettre à jour votre base de données, et intégrer proprement une validation et une logique avec votre modèle de données. Je pense que vous verrez que LINQ to SQL peut considérablement améliorer votre productivité lorsque vous travaillez avec des données, et vous permet d'écrire proprement un code d’accès aux données, orientée objet.
Dans les articles à venir, je vais traiter du nouveau contrôle .NET 3.5 <asp:linqdatasource>, et expliquer comment vous pouvez facilement construire des interfaces utilisateur de données en ASP.NET qui utilisent d’avantage les modèles de données LINQ to SQL. J'aborderai également certains concepts de programmation plus spécifique à LINQ to SQL, notamment l'accès concurrentiel optimiste, le chargement différée et impatient, l'héritage de mappage des tables, utilisation du SQL / procédures stockées personnalisées, etc. et bien plus encore…
En espérant que ceci vous aura aidé.
Scott
Traduction autorisée par Scott Guthrie.
Merci à Mitsu Furuta pour ses conseils.