IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo
Sommaire > Dataset
        Quelle différence entre Datarow.Delete et Datarows.Remove ?
        A quoi correspond l'état 'Detached' pour une ligne ?
        Peut-on exécuter une requête sur un dataset ?
        Comment générer un évènement lorsque mon dataset est modifié ?
        Comment manipuler les DataRelation et les contraintes associées pour lire les informations connexes de deux tables ?



Quelle différence entre Datarow.Delete et Datarows.Remove ?
auteur : Jean-Marc Rabilloud
Pour bien comprendre cette différence il faut appréhender correctement comment fonctionne la logique de mise à jour dans un contexte déconnecté. Je vais prendre comme exemple le DataAdapter, mais le fonctionnement général reste le même quelle que soit la technique de mises à jour.

Pour pouvoir envoyer les mises à jour vers le SGBD, le DataAdapter va parcourir la collection des lignes modifiées de la table et appliquée la commande correspondante. Le DataAdapter contient trois commandes d'action (Update, Insert et Delete). Chaque ligne contient un marqueur d'état accessible par le biais de la propriété RowState de l'objet Datarow.

La collection Datarows qui correspond aux lignes d'une table n'en reste pas moins une collection au sens DotNet. Donc lorsqu'on appelle la méthode Remove de la collection Datarows, on supprime le ligne de la collection, alors que lorsqu'on appelle la méthode Delete sur une ligne, on change sa propriété RowState en "Deleted" afin qu'elle soit supprimée du SGBD lors de la prochaine mise à jour. C'est seulement après cette mise à jour (en fait après l'appel AcceptChanges) que la ligne est aussi supprimée de la collection.

Donc pour résumé, la méthode Remove supprime la ligne de la collection Datarows sans influer sur les mises à jour suivantes tandis que Delete marque la ligne comme devant être supprimée lors de la prochaine mise à jour.

N.B : Dans la réalité, la méthode Remove supprime la ligne de la collection mais la ligne existe toujours étant marquée à l'état Detached.


A quoi correspond l'état 'Detached' pour une ligne ?
auteur : Jean-Marc Rabilloud
C'est l'état qu'il y a entre le moment de la création de la ligne et de son ajout à une table, ou après son retrait de la table après appel de la méthode Remove. Prenons l'exemple suivant :

Using MaConn As New SqlClient.SqlConnection("Data Source=ISPCF261025\SQLEXPRESS;" & _
"Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim tblAuteur As New DataTable
    Dim AuteurDataAdapter As New SqlClient.SqlDataAdapter("SELECT * FROM authors", MaConn)
    AuteurDataAdapter.Fill(tblAuteur)
    Dim NouvelAuteur As DataRow = tblAuteur.NewRow
    MessageBox.Show("L'état de la ligne ici vaut : " + NouvelAuteur.RowState.ToString)
    'Detached
    With NouvelAuteur
     .Item("au_id") = "1721"
     .Item("au_lname") = "rabilloud"
     .Item("au_fname") = "jean-marc"
     .Item("phone") = "0102030405"
    End With
    MessageBox.Show("L'état de la ligne ici vaut : " + NouvelAuteur.RowState.ToString)
    'detached
    tblAuteur.Rows.Add(NouvelAuteur)
    MessageBox.Show("L'état de la ligne ici vaut : " + NouvelAuteur.RowState.ToString)
    'Added
    tblAuteur.Rows.Remove(NouvelAuteur)
    MessageBox.Show("L'état de la ligne ici vaut : " + NouvelAuteur.RowState.ToString)
    'detached
    MaConn.Close()
End Using
Cet état Detached est rarement utilisé hors quelques cas complexes.


Peut-on exécuter une requête sur un dataset ?
auteur : Jean-Marc Rabilloud
Stricto sensu on ne peut pas, sauf dans le cadre de Linq to Dataset encore que ce soit une syntaxe particulière. On peut par contre par le biais de méthode de conteneur de données émuler certaines fonctionnalités des requêtes.

Utilisation de la méthode Select de l'objet Datatable. Celle-ci attend comme premier paramètre une chaîne de filtrage qui permet de récupérer un tableau de ligne correspondant au valeur du filtre.

Utilisation de la méthode RowFilter de l'objet Dataview qui fonctionne de manière similaire à la méthode Select mais en jouant sur la visibilité des lignes dans la vue.

L'ajout de colonne dites "d'expression" dans une table.

Voyons quelques exemples

Utilisation de Select.

Using MaConn As New SqlClient.SqlConnection("Data Source=ISPCF261025\SQLEXPRESS;" & _
"Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim Command As New SqlClient.SqlCommand("SELECT * FROM titles", MaConn)
    Dim TblTitre As New DataTable
    TblTitre.Load(Command.ExecuteReader)
    MsgBox("nombre de ligne dans la table : " + TblTitre.Rows.Count.ToString)
    Dim LignesFiltrees As DataRow() = TblTitre.Select("Type = 'business'")
    MsgBox("nombre de ligne renvoyées par le filtre : " + LignesFiltrees.Length.ToString)
    MaConn.Close()
End Using
Utilisation de RowFilter

Using MaConn As New SqlClient.SqlConnection("Data Source=ISPCF261025\SQLEXPRESS;" & _
"Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim Command As New SqlClient.SqlCommand("SELECT * FROM titles", MaConn)
    Dim TblTitre As New DataTable
    TblTitre.Load(Command.ExecuteReader)
    Me.DataGridView1.DataSource = TblTitre.DefaultView
    TblTitre.DefaultView.RowFilter = "price<20"
    MaConn.Close()
End Using
Utilisation de colonnes d'expressions

Using MaConn As New SqlClient.SqlConnection("Data Source=ISPCF261025\SQLEXPRESS;" & _
"Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim Command As New SqlClient.SqlCommand("SELECT * FROM sales", MaConn)
    Dim TblVente As New DataTable
    TblVente.Load(Command.ExecuteReader)
    Dim MaCol As DataColumn
    MaCol = New DataColumn("Somme_Qte", GetType(Int32), "SUM(qty)")
    TblVente.Columns.Add(MaCol)
    MaCol = New DataColumn("Rapport_Qte", GetType(Single), "qty/Somme_Qte")
    TblVente.Columns.Add(MaCol)
    Me.DataGridView1.DataSource = TblVente.DefaultView
    MaConn.Close()
End Using
Même si il est possible de créer des jeux de réponses relativement complexes avec ses méthodes, on est loin d'avoir la puissance d'un vrai langage de requête quand on travaille sur les Dataset. Il arrive forcément un moment où il faut se poser la question de gérer un code complexe plutôt que d'émettre une ou deux requêtes supplémentaires vers le SGBD.


Comment générer un évènement lorsque mon dataset est modifié ?
auteur : Jean-Marc Rabilloud
La modification d'un Dataset valorise la propriété HasChanges mais ne génère pas d'évènement spécifique. Cependant il est possible de générer cette évènement par le code, en statique si vous utilisez un dataset généré ou en dynamique le cas non échéant comme dans l'exemple suivant :

    Public Event HasChange(ByVal e As EventArgs)
    Private dsPubs As DataSet

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

Using MaConn As New SqlClient.SqlConnection("Data Source=ISPCF261025\SQLEXPRESS;" & _
"Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim dtaAuteur As New SqlClient.SqlDataAdapter("SELECT * from Authors", MaConn)
    Dim dtaTitre As New SqlClient.SqlDataAdapter("SELECT * from titles", MaConn)
    Dim dtaTitreAuteur As New SqlClient.SqlDataAdapter("SELECT * from titleauthor", MaConn)
    dsPubs = New DataSet("Biblio")
    dtaAuteur.Fill(dsPubs, "Auteur")
    dtaTitre.Fill(dsPubs, "Titres")
    dtaTitreAuteur.Fill(dsPubs, "TitreAuteur")
    For Each MaTable As DataTable In dsPubs.Tables
AddHandler MaTable.RowChanged, AddressOf LigneChange
AddHandler MaTable.RowDeleted, AddressOf LigneChange
AddHandler MaTable.TableNewRow, AddressOf LigneAjout
    Next
    MaConn.Close()
End Using
    End Sub

    Private Sub LigneAjout(ByVal sender As Object, ByVal e As DataTableNewRowEventArgs)
RaiseEvent HasChange(New EventArgs)
    End Sub

    Private Sub LigneChange(ByVal Sender As Object, ByVal e As DataRowChangeEventArgs)
RaiseEvent HasChange(New EventArgs)
    End Sub

    Private Sub Form1_HasChange(ByVal e As System.EventArgs) Handles Me.HasChange
MsgBox("Le dataset est modifié")
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
dsPubs.Tables("Auteur").Rows(0).Item("Contract") = 0
    End Sub
Cet exemple est une approche immonde, normalement pour faire cela, on dérive un Dataset dans lequel on implémente la gestion de l'évènement. Par ailleurs, vous aurez une notification de changement même si la valeur n'a pas été modifiée. Il faudrait exercer un contrôle de modification sur les versions de ligne pour avoir une gestion correct.

Cela reviendrait à écrire un objet dérivé tel que :

Public Class PubsDataset
    Inherits DataSet

    Public Event HasChange(ByVal e As EventArgs)
    Private m_activSurv As Boolean

    Public Property ActiveSurveillance() As Boolean
Get
    Return Me.m_activSurv
End Get
Set(ByVal value As Boolean)
    Me.m_activSurv = value
    SwitchSurveillance(value)
End Set
    End Property

    Private Sub SwitchSurveillance(ByVal Active As Boolean)
If Active Then
    For Each MaTable As DataTable In Me.Tables
AddHandler MaTable.RowChanged, AddressOf LigneChange
AddHandler MaTable.RowDeleted, AddressOf LigneChange
AddHandler MaTable.TableNewRow, AddressOf LigneAjout
    Next
Else
    For Each MaTable As DataTable In Me.Tables
RemoveHandler MaTable.RowChanged, AddressOf LigneChange
RemoveHandler MaTable.RowDeleted, AddressOf LigneChange
RemoveHandler MaTable.TableNewRow, AddressOf LigneAjout
    Next
End If
    End Sub

    Private Sub LigneAjout(ByVal sender As Object, ByVal e As DataTableNewRowEventArgs)
RaiseEvent HasChange(New EventArgs)
    End Sub

    Private Sub LigneChange(ByVal Sender As Object, ByVal e As DataRowChangeEventArgs)
Dim MaLigne As DataRow = e.Row
For cmpt As Integer = 0 To MaLigne.Table.Columns.Count - 1
    If MaLigne(cmpt, DataRowVersion.Original) IsNot MaLigne(cmpt, DataRowVersion.Current) Then
RaiseEvent HasChange(New EventArgs)
Exit For
    End If
Next
    End Sub

End Class
Que l'on pourrait consommer comme dans :

    Private WithEvents dsPubs As PubsDataset

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

Using MaConn As New SqlClient.SqlConnection("Data Source=ISPCF261025\SQLEXPRESS;" & _
"Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim dtaAuteur As New SqlClient.SqlDataAdapter("SELECT * from Authors", MaConn)
    Dim dtaTitre As New SqlClient.SqlDataAdapter("SELECT * from titles", MaConn)
    Dim dtaTitreAuteur As New SqlClient.SqlDataAdapter("SELECT * from titleauthor", MaConn)
    dsPubs = New PubsDataset()
    dtaAuteur.Fill(dsPubs, "Auteur")
    dtaTitre.Fill(dsPubs, "Titres")
    dtaTitreAuteur.Fill(dsPubs, "TitreAuteur")
    dsPubs.ActiveSurveillance = True
    MaConn.Close()
End Using
    End Sub

    Private Sub dsPubs_HasChange(ByVal e As System.EventArgs) Handles dsPubs.HasChange
MsgBox("Le dataset est modifié")
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
dsPubs.Tables("Auteur").Rows(0).Item("Contract") = False
    End Sub

Comment manipuler les DataRelation et les contraintes associées pour lire les informations connexes de deux tables ?
auteur : Jean-Marc Rabilloud
Commençons par revenir rapidement sur les fondamentaux car la documentation des mises en relation est suffisamment trompeuse pour qu'on se mélange assez facilement. Fondamentalement, les difficultés tournent une fois de plus autour de la notion de schéma dans les Dataset. Pour comprendre cela, nous allons travailler avec trois tables de la base exemple Pubs, dont le schéma pourrait être représenté tel que :

Nous allons construire le Dataset "From scratch" afin de bien comprendre le fonctionnement des relations. Contrairement à ce que la documentation peut laisser croire, les objets DataRelation ne sont pas liés aux contraintes. Autrement dit, on peut parfaitement construire au niveau d'un Dataset des relations sans que les tables contiennent des contraintes de clé primaire / clé étrangère ce qui diffère fondamentalement du fonctionnement des SGBD.

Prenons le code suivant :

Using MaConn As New SqlClient.SqlConnection("Data Source=FIXE;Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim dtaAuteur As New SqlClient.SqlDataAdapter("SELECT * from Authors", MaConn)
    Dim dtaTitre As New SqlClient.SqlDataAdapter("SELECT * from titles", MaConn)
    Dim dtaTitreAuteur As New SqlClient.SqlDataAdapter("SELECT * from titleauthor", MaConn)
    dsPubs = New DataSet("Biblio")
    dtaAuteur.Fill(dsPubs, "Auteurs")
    dtaTitre.Fill(dsPubs, "Titres")
    dtaTitreAuteur.Fill(dsPubs, "TitreAuteur")
    dsPubs.Relations.Add("Auteur_TitreAuteur", dsPubs.Tables("Auteurs").Columns("au_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("au_id"), False)
    dsPubs.Relations.Add("Titre_TitreAuteur", dsPubs.Tables("Titres").Columns("title_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("title_id"), False)
    MsgBox(dsPubs.Tables("TitreAuteur").Constraints.Count.ToString + " " & _
 + dsPubs.Tables("Auteurs").Constraints.Count.ToString + " " + & _
dsPubs.Tables("Titres").Constraints.Count.ToString)
    Dim MaCol As New DataColumn("Auteur", GetType(String), "Parent(Auteur_TitreAuteur).au_lname" & _
" + ' ' + Parent(Auteur_TitreAuteur).au_fname")
    dsPubs.Tables("TitreAuteur").Columns.Add(MaCol)
    MaCol = New DataColumn("Titre", GetType(String), "Parent(Titre_TitreAuteur).title")
    dsPubs.Tables("TitreAuteur").Columns.Add(MaCol)
    Me.DataGridView1.DataSource = dsPubs.Tables("TitreAuteur").DefaultView
    MaConn.Close()
End Using
Dans ce code, je vois bien que mes trois tables ne possèdent aucune contraintes (la boite de message affichera 0 0 0) mais que je peux mettre en relation mes trois tables puisque la grille va bien afficher les titres des livres ainsi que les nom et prénom de l'auteur.

Ceci veut dire que la seule création de la relation suffit à lire les informations connexes des tables mises en relation, en utilisant les expressions des DataColumn comme dans l'exemple précédent, ou bien les méthodes GetChildRows & GetParentRow de l'objet DataRow comme dans l'exemple suivant :

    Private dsPubs As DataSet

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

Using MaConn As New SqlClient.SqlConnection("Data Source=FIXE;Initial Catalog=pubs;" & _
"Integrated Security=True")
    MaConn.Open()
    Dim dtaAuteur As New SqlClient.SqlDataAdapter("SELECT * from Authors", MaConn)
    Dim dtaTitre As New SqlClient.SqlDataAdapter("SELECT * from titles", MaConn)
    Dim dtaTitreAuteur As New SqlClient.SqlDataAdapter("SELECT * from titleauthor", MaConn)
    dsPubs = New DataSet("Biblio")
    dtaAuteur.Fill(dsPubs, "Auteurs")
    dtaTitre.Fill(dsPubs, "Titres")
    dtaTitreAuteur.Fill(dsPubs, "TitreAuteur")
    dsPubs.Relations.Add("Auteur_TitreAuteur", dsPubs.Tables("Auteurs").Columns("au_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("au_id"), False)
    dsPubs.Relations.Add("Titre_TitreAuteur", dsPubs.Tables("Titres").Columns("title_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("title_id"), False)
    With Me.ComboBox1
.DisplayMember = "au_lname"
.ValueMember = "au_id"
.DataSource = dsPubs.Tables("Auteurs").DefaultView
    End With
    MaConn.Close()
End Using
    End Sub

    Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, & _
 ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
If Me.ComboBox1.SelectedValue IsNot Nothing Then
    Dim LigneAuteur As DataRow = dsPubs.Tables("Auteurs").Select("au_id = '" + & _
ComboBox1.SelectedValue.ToString + "'")(0)
    Dim LigneInterm() As DataRow = LigneAuteur.GetChildRows("Auteur_TitreAuteur")
    For Each ligne As DataRow In LigneInterm
Me.ListBox1.Items.Add(ligne.GetParentRow("Titre_TitreAuteur").Item("title"))
    Next
End If
    End Sub
Pour le travail avec les relations c'est donc assez simple. Cependant il peut y avoir un problème à travailler de cette façon. Imaginons que dans le code nous ayons la ligne

dsPubs.Tables("Auteurs").Rows(1).Delete()
et que nous envoyons les modifications vers le SGBD, nous allons obtenir une erreur de violation des contraintes d'intégrité. En effet, à la différence de notre Dataset, les relations du SGBD source sont gérées par des contraintes de clé étrangère qui dans le SGBD interdisent la suppression d'un enregistrement parent tant qu'il existe des enregistrements enfants.

Si nous voulons que le Dataset reproduise ce comportement afin d'anticiper les éventuelles anomalies, nous devons travailler avec les contraintes en plus des relations.

Supposons donc le code de création suivant pour notre Dataset :

Using MaConn As New SqlClient.SqlConnection("Data Source=FIXE;Initial Catalog=pubs;Integrated Security=True")
    MaConn.Open()
    Dim dtaAuteur As New SqlClient.SqlDataAdapter("SELECT * from Authors", MaConn)
    Dim dtaTitre As New SqlClient.SqlDataAdapter("SELECT * from titles", MaConn)
    Dim dtaTitreAuteur As New SqlClient.SqlDataAdapter("SELECT * from titleauthor", MaConn)
    dsPubs = New DataSet("Biblio")
    dtaAuteur.Fill(dsPubs, "Auteurs")
    dtaTitre.Fill(dsPubs, "Titres")
    dtaTitreAuteur.Fill(dsPubs, "TitreAuteur")
    dsPubs.Relations.Add("Auteur_TitreAuteur", dsPubs.Tables("Auteurs").Columns("au_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("au_id"), True)
    dsPubs.Relations.Add("Titre_TitreAuteur", dsPubs.Tables("Titres").Columns("title_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("title_id"), True)
    MsgBox(dsPubs.Tables("TitreAuteur").Constraints.Count.ToString + " " + & _
dsPubs.Tables("Auteurs").Constraints.Count.ToString + " " + dsPubs.Tables("Titres").Constraints.Count.ToString)
    MaConn.Close()
End Using
La boite de message nous annonce bien la création de 4 contraintes (2 étrangères sur la table TitreAuteur et 2 primaires sur les tables parentes) et nous voila conforme.

La si nous utilisons la ligne :

dsPubs.Tables("Auteurs").Rows(1).Delete()
Il ne se passera?..rien de plus.

Car par défaut, les contraintes créées par la méthode Add de la collection DataRelations sont réglés pour travailler en mode cascade. C'est-à-dire qu'en cas de suppression d'un enregistrement parent, les enregistrements enfants sont automatiquement supprimés aussi. Vous me direz que somme toute c'est un peu le but, mais cela n'est vrai que si l'on en est pleinement conscient, autrement cela veut dire que le Dataset agit à l'insu du développeur et que celui-ci devrait penser à se reconvertir comme dompteur d'huitre.

Pour avoir un comportement correcte, on doit donc soit laisser ainsi en sachant qu'on est en mode cascade, soit mettre la contrainte en mode 'pas d'action' soit par implémentation séparée de la contrainte, soit en écrivant :

With dsPubs.Relations.Add("Auteur_TitreAuteur", dsPubs.Tables("Auteurs").Columns("au_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("au_id"), True).ChildKeyConstraint
    .DeleteRule = Rule.None
    .AcceptRejectRule = AcceptRejectRule.None
    .UpdateRule = Rule.None
End With
With dsPubs.Relations.Add("Titre_TitreAuteur", dsPubs.Tables("Titres").Columns("title_id"), & _
 dsPubs.Tables("TitreAuteur").Columns("title_id"), True).ChildKeyConstraint
    .DeleteRule = Rule.None
    .AcceptRejectRule = AcceptRejectRule.None
    .UpdateRule = Rule.None
End With
Pour les survivants qui sont arrives jusque là sans aspirine, rappelez vous juste que dans un Dataset on peut gèrer séparément les relations et les contraintes.

N.B : Au cas où un petit malin pense qu'il suffirait de mapper le schéma de la source en appelant FillSchema, je rappelle que cette méthode ne mappe pas les contraintes de clés étrangères avec la majorité (tous ?) des fournisseurs.



Consultez les autres F.A.Q's


Valid XHTML 1.1!Valid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2009 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.