locked
revert single xml record to original state RRS feed

  • Question

  • Hey gurus....

    My little project has become quite a leviathan...  so here's the scenario:

    I have a user interface that displays records from an XML database and allows manipulation of the record. I am using 

    doc = XDocument.Load(path) to load the XML document.  As the record is changed, the changes are written to the doc... Here's the problem:  the interface allows more than one record to be displayed at a given time.  

    If the user closes one of the records, he is queried as to whether he wants to save any changes.  If so, the doc is written (and eventually saved as well, when the program closes)

     HOWEVER... if he wishes to abandon any changes, I need to reload the doc... in so doing I also lose any changes to other open records....  

    I would like to selectively reload a single record from the  source document and leave the rest of the doc (virtual copy) in its current state... 

    thanks

    Pete


    • Edited by peteepoo Saturday, August 10, 2013 7:25 PM
    Saturday, August 10, 2013 7:24 PM

Answers

  • Hi peteepoo;

    I have looked at the code and made some changes, basically I modified it to make the save changes as you go, but this is not the way I would do it. I would take a class like your recipeinfo and add a some more properties to it like a flag to state that the original data has changed and I also would have properties for original values and new values. this way before updating the document You could decide to update with new value on revert to old values. Then when you load the XML you create a recipeinfo object for each recipe when you modify the recipe you update your object. Then when you are ready to update the document you could iterate through the list and update the document and save.

    In your code you used the TextChange to save your object to the document. The problem with this is that that event is called every time you press a keystroke so if you named the fruit banana it will have executed 6 times. In the posted code below I changed it to LostFocus this way the event is called only once when you move to another control.

    I placed comments in the code please read.

    Public Class Form1
    
        Public recipepath As String = " C:\Working Directory\recipe.xml"
    
        Public doc As XDocument = XDocument.Load(recipepath)
    
        Dim myrecipeinfo1 As New recipeinfo
        Dim myrecipeinfo2 As New recipeinfo
        Dim change1 As Boolean = False
        Dim change2 As Boolean = False
        Dim recipechoice1 As String
        Dim recipechoice2 As String
    
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            loadrecipebox()
            recipebox1.SelectedIndex = 0
            recipechoice1 = recipebox1.SelectedItem  ' Initialize to what the ComboBox is set to ADDED
            recipebox2.SelectedIndex = 1
            recipechoice2 = recipebox1.SelectedItem  ' Initialize to what the ComboBox is set to ADDED
        End Sub
    
        Private Sub loadrecipebox()
            'loads the 2 comboboxes with the list of choosable recipes
    
            Dim result1 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe)
            For Each L As XElement In result1.ToList
                If recipebox1.Items.Contains(L.Value.ToString) = False Then
                    ' Seeming that both combo box have the same items and they both start out empty
                    ' you can load them both at the same time.
                    recipebox1.Items.Add(L.Value.ToString)
                    recipebox2.Items.Add(L.Value.ToString)
                End If
            Next
        End Sub
    
        Private Sub recipebox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox1.SelectedIndexChanged
    
            'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display
    
            recipechoice1 = recipebox1.SelectedItem
    
            ' Saving as you make the changes, then this is not needed
            'If change1 = True Then
            '    querysave()
            '    'doc = XDocument.Load(recipepath)
            'Else
            '    'doc = XDocument.Load(recipepath)
            'End If
    
            myrecipeinfo1.recipe = recipechoice1
            fruit1.Text = myrecipeinfo1.fruitname
            amount1.Text = myrecipeinfo1.fruitamount
    
        End Sub
    
        Private Sub recipebox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox2.SelectedIndexChanged
            'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display
    
            recipechoice2 = recipebox2.SelectedItem
    
            ' Saving as you make the changes, then this is not needed
            'If change2 = True Then
            '    querysave()
            'End If
            myrecipeinfo2.recipe = recipebox2.SelectedItem
            fruit2.Text = myrecipeinfo2.fruitname
            amount2.Text = myrecipeinfo2.fruitamount
        End Sub
    
        Private Sub fruit1_LostFocus(sender As Object, e As EventArgs) Handles fruit1.LostFocus
            'as the value is changed, it is written to  doc (this is necesary for real time calcs)
    
            If fruit1.Text <> myrecipeinfo1.fruitname Then
    
                change1 = True
                Dim newamt = (From el In doc.Descendants("recipe")
                                     Where el.Elements("name").Value = recipechoice1
                                     Select el.Element("ingredients").Element("fruit")).SingleOrDefault()
    
                If newamt IsNot Nothing Then
                    newamt.Element("name").Value = fruit1.Text
                End If
    
                querysave()  ' Added this line of code
    
            End If
        End Sub
    
        Private Sub fruit2_LostFocus(sender As Object, e As EventArgs) Handles fruit2.LostFocus
            If fruit2.Text <> myrecipeinfo2.fruitname Then
    
                change2 = True
                Dim newamt = (From el In doc.Descendants("recipe")
                                     Where el.Elements("name").Value = recipechoice2
                                     Select el.Element("ingredients").Element("fruit")).SingleOrDefault()
    
                If newamt IsNot Nothing Then
                    'rnd added to make the ingredient unique to find later
                    newamt.Element("name").Value = fruit2.Text
                    MsgBox(newamt.Element("name").Value.ToString)
                End If
    
                querysave()  ' Added this line of code
    
            End If
        End Sub
    
        Private Sub querysave()
            'checks if you want to save your changes... if yes: save the doc to the xml file then reload it... 
            ' if no: reload the doc as it is originally (revert)
    
            If MsgBox("Save Changes to the Recipe?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
                doc.Save(recipepath)
                doc = XDocument.Load(recipepath)
            Else
                doc = XDocument.Load(recipepath)
            End If
    
            change1 = False
            change2 = False
        End Sub
    
    End Class

      


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.

    Friday, August 16, 2013 4:38 PM

All replies

  • Hi peteepoo

    Can you please post all relevant code for the above question.

    Thanks


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.

    Sunday, August 11, 2013 1:05 AM
  • Fernando...

    ridiculously long program I have going on... I'll try to snippet out the relevant codes and post... I'll post when complete...


    Sunday, August 11, 2013 6:00 PM
  • Fernando,

    thanks so much for the response.  I've attached the vb code (main page and class recipeinfo) as well as the xml code ... couple of caveats:

    • this is a simplified version of my project but encapsulates the crux of my issue 
    • two sets of  recipe characteristics are shown at a time
    • The doc is updated as it is changed so other displays are easily updated in real time (I need to do it that way as far as I know)
    • My problem is in the SUB querysave:

          if I try to change the recipe name combobox and one of the fruit names has been changed (fruit1 or fruit2), you will be queried if you would like to save the changes.

     if you answer yes, ALL changes (possibly from the other recipe which you don't want saved) are saved to the doc and then reloaded... conversely, if you want to discard the changes on recipe1 but keep working on recipe 2, its going to reload BOTH recipes with the original reverted XML data.

    It seems that the solution lies in either selectively saving the single recipe node to the XML file or selectiveloy loading the single recipe node from the XML file....  I don't know how to do either...

    any help would be appreciated

    Pete Hahn

    Imports System.Xml
    Imports System.IO
    
    Public Class Form1
        Public recipepath As String = " C:\Users\pete\Desktop\shadow\shadow\recipe.xml"
    
        Public doc As XDocument = XDocument.Load(recipepath)
    
        Dim myrecipeinfo1 As New recipeinfo
        Dim myrecipeinfo2 As New recipeinfo
        Dim change1 As Boolean = False
        Dim change2 As Boolean = False
        Dim recipechoice1 As String
        Dim recipechoice2 As String
    
    
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            loadrecipebox()
            recipebox1.SelectedIndex = 0
            recipebox2.SelectedIndex = 1
        End Sub
    
        Private Sub loadrecipebox()
            'loads the 2 comboboxes with the list of choosable recipes
    
            Dim result1 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe)
            For Each L As XElement In result1.ToList
                If recipebox1.Items.Contains(L.Value.ToString) = False Then
                    recipebox1.Items.Add(L.Value.ToString)
                End If
            Next
    
            Dim result2 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe)
            For Each L As XElement In result2.ToList
                If recipebox2.Items.Contains(L.Value.ToString) = False Then
                    recipebox2.Items.Add(L.Value.ToString)
                End If
            Next
        End Sub
    
        Private Sub recipebox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox1.SelectedIndexChanged
    
            'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display
    
            recipechoice1 = recipebox1.SelectedItem
    
            If change1 = True Then
                querysave()
                'doc = XDocument.Load(recipepath)
            Else
                'doc = XDocument.Load(recipepath)
            End If
    
            myrecipeinfo1.recipe = recipechoice1
            fruit1.Text = myrecipeinfo1.fruitname
            amount1.Text = myrecipeinfo1.fruitamount
    
        End Sub
        Private Sub recipebox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox2.SelectedIndexChanged
            'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display
    
            recipechoice2 = recipebox2.SelectedItem
    
            If change2 = True Then
                querysave()
            End If
            myrecipeinfo2.recipe = recipebox2.SelectedItem
            fruit2.Text = myrecipeinfo2.fruitname
            amount2.Text = myrecipeinfo2.fruitamount
        End Sub
    
    
        Private Sub fruit1_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles fruit1.TextChanged
    
            'as the value is changed, it is written to  doc (this is necesary for real time calcs)
    
            If fruit1.Text <> myrecipeinfo1.fruitname Then
    
                change1 = True
                Dim newamt = (From el In doc.Descendants("recipe")
                                     Where el.Elements("name").Value = recipechoice1
                                     Select el.Element("ingredients").Element("fruit")).SingleOrDefault()
    
                If newamt IsNot Nothing Then
                    newamt.Element("name").Value = fruit1.Text
                End If
            End If
        End Sub
        Private Sub fruit2_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles fruit2.TextChanged
    
            'as the value is changed, it is written to  doc (this is necesary for real time calcs)
    
            If fruit2.Text <> myrecipeinfo2.fruitname Then
    
                change2 = True
                Dim newamt = (From el In doc.Descendants("recipe")
                                     Where el.Elements("name").Value = recipechoice2
                                     Select el.Element("ingredients").Element("fruit")).SingleOrDefault()
    
    
    
                If newamt IsNot Nothing Then
                    'rnd added to make the ingredient unique to find later
                    newamt.Element("name").Value = fruit2.Text
                    MsgBox(newamt.Element("name").Value.ToString)
                End If
            End If
        End Sub
    
        Private Sub querysave()
            'checks if you want to save your changes... if yes: save the doc to the xml file then reload it... 
            ' if no: reload the doc as it is originally (revert)
    
            If MsgBox("Save Changes to the Recipe?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
                doc.Save(recipepath)
                doc = XDocument.Load(recipepath)
            Else
                doc = XDocument.Load(recipepath)
            End If
    
            change1 = False
            change2 = False
        End Sub
    End Class


    Public Class recipeinfo
        Public Property recipe() As String
    
        Public ReadOnly Property fruitname() As String
            Get
                Dim query =
                      (From r In Form1.doc.Descendants("recipe")
                         Where r.Elements("name").Value = Me.recipe
                         From re In r.Descendants("fruit")
                         Select re.Elements("name").Value).SingleOrDefault()
                fruitname = If(query IsNot Nothing, query.ToString, String.Empty)
            End Get
        End Property
        Public ReadOnly Property fruitamount() As String
            Get
                Dim query =
                      (From r In Form1.doc.Descendants("recipe")
                         Where r.Elements("name").Value = Me.recipe
                         From re In r.Descendants("fruit")
                         Select re.Elements("amount").Value).SingleOrDefault()
                fruitamount = If(query IsNot Nothing, query.ToString, String.Empty)
            End Get
        End Property
    
    End Class

    <?xml version="1.0" encoding="iso-8859-1"?>
    <recipes>
      <recipe>
        <name>gmas apple pie</name>
        <type>pie</type>
        <date>08/15/2013</date>
        <ingredients>
          <fruit>
            <name>apple</name>
            <amount>5</amount>
          </fruit>
        </ingredients>
      </recipe>
      <recipe>
        <name>lemonade</name>
        <type>drink</type>
        <date>08/15/2013</date>
        <ingredients>
          <fruit>
            <name>lemon</name>
            <amount>4</amount>
          </fruit>
          <sweetener>
            <name>aspartame</name>
            <amount>3 tsp</amount>
          </sweetener>
        </ingredients>
      </recipe>
      <recipe>
        <name>my marmalade</name>
        <type>jam</type>
        <date>08/15/2013</date>
        <ingredients>
          <fruit>
            <name>orange</name>
            <amount>3</amount>
          </fruit>
          <sweetener>
            <name>cane sugar</name>
            <amount>2 cups</amount>
          </sweetener>
        </ingredients>
      </recipe>
    </recipes>





    • Edited by peteepoo Thursday, August 15, 2013 10:14 PM
    Thursday, August 15, 2013 10:08 PM
  • Hi peteepoo;

    I have looked at the code and made some changes, basically I modified it to make the save changes as you go, but this is not the way I would do it. I would take a class like your recipeinfo and add a some more properties to it like a flag to state that the original data has changed and I also would have properties for original values and new values. this way before updating the document You could decide to update with new value on revert to old values. Then when you load the XML you create a recipeinfo object for each recipe when you modify the recipe you update your object. Then when you are ready to update the document you could iterate through the list and update the document and save.

    In your code you used the TextChange to save your object to the document. The problem with this is that that event is called every time you press a keystroke so if you named the fruit banana it will have executed 6 times. In the posted code below I changed it to LostFocus this way the event is called only once when you move to another control.

    I placed comments in the code please read.

    Public Class Form1
    
        Public recipepath As String = " C:\Working Directory\recipe.xml"
    
        Public doc As XDocument = XDocument.Load(recipepath)
    
        Dim myrecipeinfo1 As New recipeinfo
        Dim myrecipeinfo2 As New recipeinfo
        Dim change1 As Boolean = False
        Dim change2 As Boolean = False
        Dim recipechoice1 As String
        Dim recipechoice2 As String
    
    
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            loadrecipebox()
            recipebox1.SelectedIndex = 0
            recipechoice1 = recipebox1.SelectedItem  ' Initialize to what the ComboBox is set to ADDED
            recipebox2.SelectedIndex = 1
            recipechoice2 = recipebox1.SelectedItem  ' Initialize to what the ComboBox is set to ADDED
        End Sub
    
        Private Sub loadrecipebox()
            'loads the 2 comboboxes with the list of choosable recipes
    
            Dim result1 As IEnumerable(Of XElement) = (From xe In doc.Descendants("recipe").Elements("name") Select xe)
            For Each L As XElement In result1.ToList
                If recipebox1.Items.Contains(L.Value.ToString) = False Then
                    ' Seeming that both combo box have the same items and they both start out empty
                    ' you can load them both at the same time.
                    recipebox1.Items.Add(L.Value.ToString)
                    recipebox2.Items.Add(L.Value.ToString)
                End If
            Next
        End Sub
    
        Private Sub recipebox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox1.SelectedIndexChanged
    
            'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display
    
            recipechoice1 = recipebox1.SelectedItem
    
            ' Saving as you make the changes, then this is not needed
            'If change1 = True Then
            '    querysave()
            '    'doc = XDocument.Load(recipepath)
            'Else
            '    'doc = XDocument.Load(recipepath)
            'End If
    
            myrecipeinfo1.recipe = recipechoice1
            fruit1.Text = myrecipeinfo1.fruitname
            amount1.Text = myrecipeinfo1.fruitamount
    
        End Sub
    
        Private Sub recipebox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles recipebox2.SelectedIndexChanged
            'sets the public property "recipe" in the recipeinfo class to the selected value and returns the values for display
    
            recipechoice2 = recipebox2.SelectedItem
    
            ' Saving as you make the changes, then this is not needed
            'If change2 = True Then
            '    querysave()
            'End If
            myrecipeinfo2.recipe = recipebox2.SelectedItem
            fruit2.Text = myrecipeinfo2.fruitname
            amount2.Text = myrecipeinfo2.fruitamount
        End Sub
    
        Private Sub fruit1_LostFocus(sender As Object, e As EventArgs) Handles fruit1.LostFocus
            'as the value is changed, it is written to  doc (this is necesary for real time calcs)
    
            If fruit1.Text <> myrecipeinfo1.fruitname Then
    
                change1 = True
                Dim newamt = (From el In doc.Descendants("recipe")
                                     Where el.Elements("name").Value = recipechoice1
                                     Select el.Element("ingredients").Element("fruit")).SingleOrDefault()
    
                If newamt IsNot Nothing Then
                    newamt.Element("name").Value = fruit1.Text
                End If
    
                querysave()  ' Added this line of code
    
            End If
        End Sub
    
        Private Sub fruit2_LostFocus(sender As Object, e As EventArgs) Handles fruit2.LostFocus
            If fruit2.Text <> myrecipeinfo2.fruitname Then
    
                change2 = True
                Dim newamt = (From el In doc.Descendants("recipe")
                                     Where el.Elements("name").Value = recipechoice2
                                     Select el.Element("ingredients").Element("fruit")).SingleOrDefault()
    
                If newamt IsNot Nothing Then
                    'rnd added to make the ingredient unique to find later
                    newamt.Element("name").Value = fruit2.Text
                    MsgBox(newamt.Element("name").Value.ToString)
                End If
    
                querysave()  ' Added this line of code
    
            End If
        End Sub
    
        Private Sub querysave()
            'checks if you want to save your changes... if yes: save the doc to the xml file then reload it... 
            ' if no: reload the doc as it is originally (revert)
    
            If MsgBox("Save Changes to the Recipe?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
                doc.Save(recipepath)
                doc = XDocument.Load(recipepath)
            Else
                doc = XDocument.Load(recipepath)
            End If
    
            change1 = False
            change2 = False
        End Sub
    
    End Class

      


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

    NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.

    Friday, August 16, 2013 4:38 PM