DataSet and serialization is dangerous

G

Guest

(Above there is the whole code.)
I have a dataset with 2 tables (father and child table) and a
relation between them with cascade for update and delete.
On this DataSet I delete a existing father's row and afterwards create the
same father's row with the same field values and also create a child row.
Afterwards, I execute the serialization of this DataSet to a file and, and
the deserialization to another DataSet. You will see that the deserialized
DataSet doesn't has the chid row (it was vanished).
But note that the child row has vanished only if you create the father's
row with the same field's value from the deleted father's row.
This is a terrible problem. How can we use this technology in a serious
application?
Can someone help me?

*** The code ***
It is necessary to create a Form (Form1), copy the variable declarations and
copy the statements inside the Form1_Load().
Thanks.
Mauricio Pires
ControlBase (Brazil)

'Variable declarations"
Friend FatherTable As New DataTable("FatherTable")
Friend ChildTable As New DataTable("ChildTable")

Friend dsOriginal As New DataSet
Friend dsRead As DataSet
Friend WithEvents DataGridOriginalFather As System.Windows.Forms.DataGrid
Friend WithEvents DataGridOriginalChild As System.Windows.Forms.DataGrid

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

Me.ClientSize = New System.Drawing.Size(600, 266)

'Creating the DataGrids
Me.DataGridOriginalFather = New System.Windows.Forms.DataGrid
Me.DataGridOriginalFather.CaptionText = "Original Father"
Me.DataGridOriginalFather.Location = New System.Drawing.Point(56, 24)
Me.DataGridOriginalFather.Name = "DataGridOriginalFather"
Me.DataGridOriginalFather.Size = New System.Drawing.Size(184, 112)

Me.DataGridOriginalChild = New System.Windows.Forms.DataGrid
Me.DataGridOriginalChild.CaptionText = "Original Child"
Me.DataGridOriginalChild.Location = New System.Drawing.Point(40, 168)
Me.DataGridOriginalChild.Name = "DataGridOriginalChild"
Me.DataGridOriginalChild.Size = New System.Drawing.Size(232, 80)

Me.DataGridDeserializedFather = New System.Windows.Forms.DataGrid
Me.DataGridDeserializedFather.CaptionText = "Deserialized Father"
Me.DataGridDeserializedFather.Location = New
System.Drawing.Point(344, 24)
Me.DataGridDeserializedFather.Name = "DataGridDeserializedFather"
Me.DataGridDeserializedFather.Size = New System.Drawing.Size(184, 120)

Me.DataGridDeserializedChild = New System.Windows.Forms.DataGrid
Me.DataGridDeserializedChild.CaptionText = "Deserialized Child (the
child vanished)"
Me.DataGridDeserializedChild.HeaderForeColor =
System.Drawing.SystemColors.ControlText
Me.DataGridDeserializedChild.Location = New
System.Drawing.Point(328, 168)
Me.DataGridDeserializedChild.Name = "DataGridDeserializedChild"
Me.DataGridDeserializedChild.Size = New System.Drawing.Size(232, 80)

Me.Controls.Add(Me.DataGridOriginalChild)
Me.Controls.Add(Me.DataGridOriginalFather)
Me.Controls.Add(Me.DataGridDeserializedFather)
Me.Controls.Add(Me.DataGridDeserializedChild)

'Creating 2 tables (father and child tables) and a relation
Dim fatherCol1 As New DataColumn("FatherCol1")
Dim childCol1 As New DataColumn("ChildCol1")
Dim childCol2 As New DataColumn("ChildCol2")

FatherTable.Columns.Add(fatherCol1)
ChildTable.Columns.Add(childCol1)
ChildTable.Columns.Add(childCol2)

dsOriginal.Tables.Add(FatherTable)
dsOriginal.Tables.Add(ChildTable)
dsOriginal.Relations.Add("FatherChildRelation", fatherCol1, childCol1)

'Creating a father row
FatherTable.Rows.Add(New Object() {"Father1"})
Me.dsOriginal.AcceptChanges()

'Deleting the father row that was created above
FatherTable.Rows(0).Delete()

'Creating the same father row again
FatherTable.Rows.Add(New Object() {"Father1"})

'Creating a child row
ChildTable.Rows.Add(New Object() {"Father1", "Child1"})

'Serialization to "myFile.xml"
Dim mySerializer As New
System.Xml.Serialization.XmlSerializer(GetType(DataSet))
Dim myWriter As IO.StreamWriter = New
IO.StreamWriter("myFileName.xml")
mySerializer.Serialize(myWriter, dsOriginal)
myWriter.Close()

'Deserialization (see that dataset lost the child row)
Dim myFileStream As IO.FileStream = New
IO.FileStream("myFileName.xml", IO.FileMode.Open)
' Calls the Deserialize.
dsRead = CType( _
mySerializer.Deserialize(myFileStream), DataSet)
myFileStream.Close()

'This DataDrid show the DataSet with one father and one child rows
(OK)
Me.DataGridOriginalFather.SetDataBinding(dsOriginal, "FatherTable")
Me.DataGridOriginalChild.SetDataBinding(dsOriginal,
"FatherTable.FatherChildRelation")

'These DataGrids show the dataset with one father and anyone chid
row (The child vanished)
Me.DataGridDeserializedFather.SetDataBinding(Me.dsRead, "FatherTable")
Me.DataGridDeserializedChild.SetDataBinding(Me.dsRead,
"FatherTable.FatherChildRelation")
End Sub

'------ THE END -------------
 
C

Cor Ligthert

Mauricio,

Did you try already the build in dataset.xmlwrite and dataset.xmlread for
the serialization.

(There are more overloaded methods to add by instance the schema and even
the currentstates when there are still updates to do).

I hope this helps?

Cor
 
G

Guest

Hi Cor,
No, this doesn't help because this serialization is used implicit when we
call a remote application method.
 
C

Cor Ligthert

Mauricio,

Strange,

In my opinion can you use this in anyway
\\\
Public Class Whatever
Public Shared Sub Main()
Dim ds As New DataSet
Dim dt As New DataTable
ds.Tables.Add(dt)
dt.Columns.Add()
dt.Rows.Add(dt.NewRow)
dt.Rows(0).Item(0) = "Hello"
Dim sw As New System.IO.StringWriter
ds.WriteXml(sw, XmlWriteMode.WriteSchema)
Dim mystring As String = sw.ToString
Dim sr As New System.IO.StringReader(mystring)
Dim ds2 As New DataSet
ds2.ReadXml(sr, XmlReadMode.ReadSchema)
MessageBox.Show(ds.Tables(0).Rows(0)(0).ToString)
End Sub
///
What has your code more?

Cor
 
G

Guest

Hi Cor,
I created my example to show the problem. Actually I have 2 applications
(client and server) and when a call a server's method passing along the
dataset, the child row vanishes in the server's method.
I call the server's method by remoting so the serialization happens.
This is the problem.
Thanks.

Mauricio.
 
P

Patrice

Could it be that there is no way to know if the child belongs to the
original deleted row or to the newly created row.

What if you have a unique key in the dataset. Does it work then ?

Patrice

--
 
G

Guest

Even with unique key in dataset it doesn't work.
Did you notice that the child only vanishes if the father row has the same
deleted father value. If you input a father row with other values, it's ok.
Mauricio
Thanks.
 
G

Guest

No, the problem happens even with a unique key.
Notice that the problem only happens if the new father row has the same
values from the deleted father row. if the new father row has different field
values, it's ok.
Thanks.
Mauricio.
 
P

Patrice

This is precisely (that it works if the value is different but fails if this
is the same) what makes me think that you wouldn't have this problem with a
"true" pk.
You'll have to establish the relation on this new pk field. I 'm assuming
also that you never reuse an already used value for this pk field (basically
autoincrement or guid) as this is done usually for PKs...

Also what if you call AcceptChanges just before serializing ? It would
remove the deleted row and could perhaps solve the problem ?

Patrice


--
 
G

Guest

No, I can't call AcceptChanges() because I will lose the modified, deleted
and inserted rows which are passed to my server application.
 
A

ABad

You may have to look deeper into the different serialization/deserialization
techniques and the options with each one, and how their round-trip usage
produces results that may differ with the original in-memory representation.

Using the technique you have shown the resulting XML file has the previous
version with only the father, but it also has the new dataset version with
the father and child relationship. So the data isn't lost, but the
serialization and deserialization technique you have chosen may cause
different in-memory representations than the original dataset. Looking at
the GUI results thats my guess on what is happening. The read dataset is
showing only the previous version while the original is showing the new
dataset version.

If you change your serialization/deserialization technique to:
- dsOriginal.WriteXml("myFileName.xml",XmlWriteMode.WriteSchema)
- dsRead = new Dataset
- dsRead.ReadXML("myFileName.xml",XmlReadMode.Auto)

You get the results you expect. This shows this
serialization/deserialization technique produces what the original dataset
had in-memory.

Just an observation.

- ABad
 
A

ABad

OK, I just did another test with your code modified, and you are confusing
what is happening with what you think should be happening.

On the form load I do only the original father/child dataset grids, add
Father1 to the dataset and delete Father1, nothing more. I manually, through
the UI grid, add Father1 back, add a child with Father1 as the father, click
the add button. This kicks off the button click event. This event handler
adds the two deserialized grids, serializes/deserializes the original
dataset, and sets the two deserialized grids datasource to the new read-in
dataset. The results are the same as before, basically what you have shown
in your previous example.

What this shows me is that the changes to the original grid/dataset are
tracked, in-memory and shown by the UI, and the
serialization/deserialization/UI technique chosen for your test only takes
in effect the previous version before the accept changes, not the changed
state after the accept changes with the new data.

When you read in the dataset you have to know whether you want to show the
before results or the modified results. It looks like the default is the
before results. In other words instead of binding to the dsRead dataset do
"Dim ChangesDS as Dataset = dsRead.GetChanges()" and then bind to ChangeDS.
You get the results you are looking for.

Code below.

- ABad

Friend WithEvents TheButton as Button

Private Sub TheButton_Click(sender as Object, e as EventArgs) Handles
TheButton.Click
Me.DataGridDeserializedFather = New System.Windows.Forms.DataGrid
Me.DataGridDeserializedFather.CaptionText = "Deserialized Father"
Me.DataGridDeserializedFather.Location = New System.Drawing.Point(344,
24)
Me.DataGridDeserializedFather.Name = "DataGridDeserializedFather"
Me.DataGridDeserializedFather.Size = New System.Drawing.Size(184, 120)
Me.DataGridDeserializedChild = New System.Windows.Forms.DataGrid
Me.DataGridDeserializedChild.CaptionText = "Deserialized Child (the
child vanished)"
Me.DataGridDeserializedChild.HeaderForeColor =
System.Drawing.SystemColors.ControlText
Me.DataGridDeserializedChild.Location = New System.Drawing.Point(328,
168)
Me.DataGridDeserializedChild.Name = "DataGridDeserializedChild"
Me.DataGridDeserializedChild.Size = New System.Drawing.Size(232, 80)
Me.Controls.Add(Me.DataGridDeserializedFather)
Me.Controls.Add(Me.DataGridDeserializedChild)
'Serialization to "myFile.xml"
Dim mySerializer As New
System.Xml.Serialization.XmlSerializer(GetType(DataSet))
Dim myWriter As IO.StreamWriter = New IO.StreamWriter("myFileName.xml")
mySerializer.Serialize(myWriter, dsOriginal)
myWriter.Close()
'Deserialization (see that dataset lost the child row)
Dim myFileStream As IO.FileStream = New IO.FileStream("myFileName.xml",
IO.FileMode.Open)
dsRead = CType( mySerializer.Deserialize(myFileStream), DataSet)
myFileStream.Close()

Dim ChangesDS as Dataset = dsRead.GetChanges()
'These DataGrids show the dataset with one father and anyone chid row
(The child vanished)
Me.DataGridDeserializedFather.SetDataBinding(ChangesDS, "FatherTable")
Me.DataGridDeserializedChild.SetDataBinding(ChangesDS,
"FatherTable.FatherChildRelation")
End Sub

Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Me.ClientSize = New System.Drawing.Size(600, 266)
TheButton = New Button()
TheButton.text = "Add Child"
Me.Controls.Add(TheButton)
'Creating the DataGrids
Me.DataGridOriginalFather = New System.Windows.Forms.DataGrid
Me.DataGridOriginalFather.CaptionText = "Original Father"
Me.DataGridOriginalFather.Location = New System.Drawing.Point(56, 24)
Me.DataGridOriginalFather.Name = "DataGridOriginalFather"
Me.DataGridOriginalFather.Size = New System.Drawing.Size(184, 112)
Me.DataGridOriginalChild = New System.Windows.Forms.DataGrid
Me.DataGridOriginalChild.CaptionText = "Original Child"
Me.DataGridOriginalChild.Location = New System.Drawing.Point(40, 168)
Me.DataGridOriginalChild.Name = "DataGridOriginalChild"
Me.DataGridOriginalChild.Size = New System.Drawing.Size(232, 80)

Me.Controls.Add(Me.DataGridOriginalChild)
Me.Controls.Add(Me.DataGridOriginalFather)

'Creating 2 tables (father and child tables) and a relation
Dim fatherCol1 As New DataColumn("FatherCol1")
Dim childCol1 As New DataColumn("ChildCol1")
Dim childCol2 As New DataColumn("ChildCol2")
FatherTable.Columns.Add(fatherCol1)
ChildTable.Columns.Add(childCol1)
ChildTable.Columns.Add(childCol2)
dsOriginal.Tables.Add(FatherTable)
dsOriginal.Tables.Add(ChildTable)
dsOriginal.Relations.Add("FatherChildRelation", fatherCol1, childCol1)
'Creating a father row
FatherTable.Rows.Add(New Object() {"Father1"})
Me.dsOriginal.AcceptChanges()
'Deleting the father row that was created above
FatherTable.Rows(0).Delete()
'This DataDrid show the DataSet with one father and one child rows (OK)
Me.DataGridOriginalFather.SetDataBinding(dsOriginal, "FatherTable")
Me.DataGridOriginalChild.SetDataBinding(dsOriginal,
"FatherTable.FatherChildRelation")
End Sub
 
A

ABad

Aaack...it must be late. I offer my sincerest apologies, I just tried the
test with a new father (Father2) and child (Child2) and I see what you mean.

But here is the thing, look at the XML that is produced:

The results from Father1 only test:

<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<NewDataSet>
<FatherTable diffgr:id="FatherTable2" msdata:rowOrder="1"
diffgr:hasChanges="inserted">
<FatherCol1>Father1</FatherCol1>
</FatherTable>
<FatherTable diffgr:id="FatherTable3" msdata:rowOrder="2"
diffgr:hasChanges="inserted" />
<ChildTable diffgr:id="ChildTable1" msdata:rowOrder="0"
diffgr:hasChanges="inserted">
<ChildCol1>Father1</ChildCol1>
<ChildCol2>child1</ChildCol2>
</ChildTable>
</NewDataSet>
<diffgr:before>
<FatherTable diffgr:id="FatherTable1" msdata:rowOrder="0">
<FatherCol1>Father1</FatherCol1>
</FatherTable>
</diffgr:before>
</diffgr:diffgram>

The results from Father2 only test:

<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<NewDataSet>
<FatherTable diffgr:id="FatherTable2" msdata:rowOrder="1"
diffgr:hasChanges="inserted">
<FatherCol1>Father2</FatherCol1>
</FatherTable>
<FatherTable diffgr:id="FatherTable3" msdata:rowOrder="2"
diffgr:hasChanges="inserted" />
<ChildTable diffgr:id="ChildTable1" msdata:rowOrder="0"
diffgr:hasChanges="inserted">
<ChildCol1>Father2</ChildCol1>
<ChildCol2>child2</ChildCol2>
</ChildTable>
</NewDataSet>
<diffgr:before>
<FatherTable diffgr:id="FatherTable1" msdata:rowOrder="0">
<FatherCol1>Father1</FatherCol1>
</FatherTable>
</diffgr:before>
</diffgr:diffgram>

Schema wise they are identical only the contents are different in the
<NewDataSet>, but Father1 test shows the before results (<diffgr:before>),
Father2 test shows the changed results (<NewDataSet>). Matter of fact I
removed the previous version (<diffgr:before>)of the Father1 test, read it
in and it displayed with the child results. So its not that the
serialization is doing anything different, its the datagrid is showing one
over the other when there is a previous version versus a changed version. Is
this a property or default behavior of the DataGrid when reading back in,
got me? Do you have another test where you are getting this problem where
the datagrid or no other UI controls are involved?

- ABad
 
G

Guest

Hi ABad,
I continue thinking the problem is on the serialization and no in the
datagrid.
Forget the datagrid and compare the dsOriginal.Tables("ChildTable") and
dsRead.Tables("ChildTables") after deserialization. You will see that
dsOriginal.Tables("ChildTable").Count is different from
dsRead.Tables("ChildTable").Count.
Actually, I'm passing the dataset changes to a server application which save
the changes in a SQL Server, and I am losing data.
Thanks.
Mauricio.
 
A

ABad

Ok, doing more research. In "Applied XML Programming for Microsoft.NET" by
Dino Esposito, under a section called "Reading Back DiffGrams", he goes into
detail:

<SNIP>
When reading a DiffGram, the DataSet object's ReadXml method first loads the
data instance and creates all the necessary tables and rows. Each row is put
in the added or modified state, as appropriate. All the diffgr:id values are
temporarily copied into an internal hash table defined as a property of the
DataSet object. Each entry in the hash table references a DataRow object in
the table being created.

Next ReadXml processes the <diffgr:before> section and reads the old values
for the available rows. If a match can be found between a row in the
<diffgr:before> section and a row already loaded in the table, the just-read
values are stored as the original values of the table row. ReadXml looks for
a match between the diffgr:id attribute in the <diffgr:before> section and
the contents of the hash table. Figure 10-2 shows how the DataSet object is
built.
</SNIP>

What he is saying here is that the <diffgr:before> is processed after the
modified section of the diffgram is processed. When I read this, I attached
events to the dsRead dataset to spit out if any rows were being added and
deleted. Lo and behold it looks like what Dino has stated is correct.

The output I get is:

<SNIP>
Parent_Row_Changing Event: name=Father1; action=Add; state=Detached
Parent_Row_Changed Event: name=Father1; action=Add; state=Added
Child_Row_Changing Event: name=Child1; action=Add; state=Detached
Child_Row_Changed Event: name=Child1; action=Add; state=Added
Parent_Row_Changing Event: name=Father1; action=Add; state=Unchanged
Parent_Row_Changed Event: name=Father1; action=Add; state=Modified
Parent_Row_Deleting Event: name=Father1; action=Delete; state=Modified
Child_Row_Deleting Event: name=Child1; action=Delete; state=Added
</SNIP>

You can see that indeed the detached father/child are being added, but after
the original Father is added, it goes and deletes the detached Father/child.

I would argue whether you are actually losing data. The Serialized output
has the current state of the dataset, I can open the file and see it, thats
the way your server recieves it. Its when your server uses the dataset
deserialization rules that the newly added Parent/Child relationship is
thrown away and reverting to the original state of Father.

Code below.

-ABad


Imports System.Data.SqlClient
Imports System.Collections.Specialized
Module Module1

Sub CreateSchema(ByVal InDS As DataSet)
Dim FatherTable As New DataTable("FatherTable")
Dim ChildTable As New DataTable("ChildTable")
Dim fatherCol1 As New DataColumn("FatherCol1")
Dim childCol1 As New DataColumn("ChildCol1")
Dim childCol2 As New DataColumn("ChildCol2")
FatherTable.Columns.Add(fatherCol1)
ChildTable.Columns.Add(childCol1)
ChildTable.Columns.Add(childCol2)
InDS.Tables.Add(FatherTable)
InDS.Tables.Add(ChildTable)
InDS.Relations.Add("FatherChildRelation", fatherCol1, childCol1)
End Sub

Private Sub Row_Changed(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Child_Row_Changed Event: name={0}; action={1};
state={2}", _
e.Row("ChildCol2"), e.Action, e.Row.RowState)
End Sub
Private Sub Row_Changing(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Child_Row_Changing Event: name={0}; action={1};
state={2}", _
e.Row("ChildCol2"), e.Action, e.Row.RowState)
End Sub
Private Sub Row_Deleted(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Child_Row_Deleted Event: name={0}; action={1};
state={2}", _
e.Row("ChildCol2"), e.Action, e.Row.RowState)
End Sub
Private Sub Row_Deleting(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Child_Row_Deleting Event: name={0}; action={1};
state={2}", _
e.Row("ChildCol2"), e.Action, e.Row.RowState)
End Sub
Private Sub Row_ParentChanging(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Parent_Row_Changing Event: name={0}; action={1};
state={2}", _
e.Row("FatherCol1"), e.Action, e.Row.RowState)
End Sub
Private Sub Row_ParentDeleting(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Parent_Row_Deleting Event: name={0}; action={1};
state={2}", _
e.Row("FatherCol1"), e.Action, e.Row.RowState)
End Sub
Private Sub Row_ParentChanged(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Parent_Row_Changed Event: name={0}; action={1};
state={2}", _
e.Row("FatherCol1"), e.Action, e.Row.RowState)
End Sub
Private Sub Row_ParentDeleted(ByVal sender As Object, ByVal e As
DataRowChangeEventArgs)
Console.WriteLine("Parent_Row_Deleted Event: name={0}; action={1};
state={2}", _
e.Row("FatherCol1"), e.Action, e.Row.RowState)
End Sub
Sub Main()
Dim dsOriginal As New DataSet
CreateSchema(dsOriginal)
'Creating a father row
dsOriginal.Tables("FatherTable").Rows.Add(New Object() {"Father1"})
dsOriginal.AcceptChanges()
'Deleting the father row that was created above
dsOriginal.Tables("FatherTable").Rows(0).Delete()
'Creating the same father row again
dsOriginal.Tables("FatherTable").Rows.Add(New Object() {"Father1"})
'Creating a child row
dsOriginal.Tables("ChildTable").Rows.Add(New Object() {"Father1",
"Child1"})
dsOriginal.WriteXml("myFileName.xml", XmlWriteMode.DiffGram)
Dim dsRead As New DataSet
CreateSchema(dsRead)
AddHandler dsRead.Tables("ChildTable").RowChanged, AddressOf Row_Changed
AddHandler dsRead.Tables("ChildTable").RowChanging, AddressOf
Row_Changing
AddHandler dsRead.Tables("ChildTable").RowDeleting, AddressOf
Row_Deleting
AddHandler dsRead.Tables("ChildTable").RowDeleted, AddressOf Row_Deleted
AddHandler dsRead.Tables("FatherTable").RowChanging, AddressOf
Row_ParentChanging
AddHandler dsRead.Tables("FatherTable").RowDeleting, AddressOf
Row_ParentDeleting
AddHandler dsRead.Tables("FatherTable").RowChanged, AddressOf
Row_ParentChanged
AddHandler dsRead.Tables("FatherTable").RowDeleted, AddressOf
Row_ParentDeleted
dsRead.ReadXml("myFileName2.xml", XmlReadMode.DiffGram)
Console.WriteLine("")
Console.WriteLine("**** ORIGINAL
*****************************************************")
Console.WriteLine("")
dsOriginal.WriteXml(Console.Out)

Console.WriteLine("")
Console.WriteLine("**** New
*****************************************************")
Console.WriteLine("")
dsRead.WriteXml(Console.Out)
Console.ReadLine()
End Sub
End Module
 
G

Guest

Hi ABad,
Yes, the problem is in deserialization, but deserialization is done by
remoting process. Look at my example after deserialization (after load
dsRead) and you will see that dsOriginal.Tables("ChildTable").Rows.Count=1
and dsRead.Tables("ChildTable").Rows.Count=0. Why? Where is the child row?
Thanks a lot for your attention. I need a help.

Mauricio.
 
A

ABad

Look closely at the XML diffgram that is being passed to the server (or the
file from your example).

<diffgr:before>
<FatherTable diffgr:id="FatherTable1" msdata:rowOrder="0">
<FatherCol1>Father1</FatherCol1>
</FatherTable>
</diffgr:before>

This snippet above says the original state of the Father table had Father1.

<NewDataSet>
<FatherTable diffgr:id="FatherTable2" msdata:rowOrder="1"
diffgr:hasChanges="inserted">
<FatherCol1>Father1</FatherCol1>
</FatherTable>
<ChildTable diffgr:id="ChildTable1" msdata:rowOrder="0"
diffgr:hasChanges="inserted">
<ChildCol1>Father1</ChildCol1>
<ChildCol2>Child1</ChildCol2>
</ChildTable>
</NewDataSet>

This snippet above says the proposed changes are *insert* into Father table
Father1, and insert into Child table Father1/Child1.

The dataset when deserializing will first apply the <NewDataSet>. Then when
it applies the original state <diffgr:before>, it sees Father1 already
existed, and therefore the *insert* by <NewDataSet> can't be possible and
undoes the insert specified by the <NewDataSet>. Because of the cascading
deletes the child inserted from <NewDataSet> is also undone. So the reason
you are not seeing Child1 is because the deserializing dataset is detecting
a constraint conflict between the proposed changes and the original state in
your diffgram, and rolling back to the original state.

The pattern of usage I have seen before is:
- Server has original dataset
- Client recieves the original dataset
- Client makes changes to the dataset
- Client sends the *only the changes* back to the server
- Server receives the changes, merges them into the original dataset, saves
the changes, accepts changes

Example code it would be:
Dim dsMain As DataSet = objWebService.GetDataSet()
ModifyDataSetContents(dsMain)
Dim dsChanges As DataSet = dsMain.GetChanges()
dsChanges = objWebService.SubmitChanges(dsChanges)
dsMain.Merge(dsChanges)
dsMain.AcceptChanges()

I have modified your example to reflect this pattern and the code is at the
end of this message. Note for the merge to be able to take place the Father
Table had to have the PrimaryKey property set. The resulting diffgram of the
modified example is:

<diffgr:before>
<FatherTable diffgr:id="FatherTable1" msdata:rowOrder="0">
<FatherCol1>Father1</FatherCol1>
</FatherTable>
</diffgr:before>

This snippet looks the same as the one up above.

<NewDataSet>
<FatherTable diffgr:id="FatherTable1" msdata:rowOrder="0"
diffgr:hasChanges="modified">
<FatherCol1>Father1</FatherCol1>
</FatherTable>
<ChildTable diffgr:id="ChildTable1" msdata:rowOrder="0"
diffgr:hasChanges="inserted">
<ChildCol1>Father1</ChildCol1>
<ChildCol2>Child1</ChildCol2>
</ChildTable>
</NewDataSet>

This snippet has changed from the one up above. For one the diffgr:id of the
Father1 row is identical to the diffgr:before diffgr:id of the Father1 row.
Also Father1 diffgr:hasChanges has been changed to modified instead of
inserted. This is the result you want when you send the diffgram back to the
server.

- ABad


Sub CreateSchema(ByVal InDS As DataSet)
Dim FatherTable As New DataTable("FatherTable")
Dim ChildTable As New DataTable("ChildTable")
Dim fatherCol1 As New DataColumn("FatherCol1")
Dim childCol1 As New DataColumn("ChildCol1")
Dim childCol2 As New DataColumn("ChildCol2")
FatherTable.Columns.Add(fatherCol1)
ChildTable.Columns.Add(childCol1)
ChildTable.Columns.Add(childCol2)
FatherTable.PrimaryKey = New DataColumn() {fatherCol1}
InDS.Tables.Add(FatherTable)
InDS.Tables.Add(ChildTable)
InDS.Relations.Add("FatherChildRelation", fatherCol1, childCol1)
End Sub
Function CreateOriginalDataset() As DataSet
Dim dsOriginal As New DataSet
CreateSchema(dsOriginal)
dsOriginal.Tables("FatherTable").Rows.Add(New Object() {"Father1"})
dsOriginal.AcceptChanges()
Return dsOriginal
End Function

Sub WorkWithDataset(ByVal WorkingDataSet As DataSet)
WorkingDataSet.Tables("FatherTable").Rows(0).Delete()
WorkingDataSet.Tables("FatherTable").Rows.Add(New Object() {"Father1"})
WorkingDataSet.Tables("ChildTable").Rows.Add(New Object() {"Father1",
"Child1"})
End Sub

Sub Main()
'dsOriginal is the server copy
Dim dsOriginal As DataSet = CreateOriginalDataset()

'dsWorking is the client copy
'starts off with original copy
Dim dsWorking As DataSet = dsOriginal.Copy
'client copy is modified
WorkWithDataset(dsWorking)

'merge client side changes to the server copy
dsOriginal.Merge(dsWorking.GetChanges)
'persist out
dsOriginal.WriteXml("myFileName.xml", XmlWriteMode.DiffGram)

'read back in changes
Dim dsRead As New DataSet
CreateSchema(dsRead)
dsRead.ReadXml("myFileName.xml", XmlReadMode.DiffGram)

Console.WriteLine("")
Console.WriteLine("**** ORIGINAL
*****************************************************")
Console.WriteLine("")
dsOriginal.WriteXml(Console.Out)

Console.WriteLine("")
Console.WriteLine("**** New
*****************************************************")
Console.WriteLine("")
dsRead.WriteXml(Console.Out)
Console.ReadLine()
End Sub
 
G

Guest

Hi ABad,
Excuse me, but I think your code doesn't actually represent the remoting
process (or am I wrong?).
I did some changes in your code (sub Main() only) that I think be more
similar to remoting process. The child row vanished.
Thanks.

Sub Main()
'dsOriginal is the server copy
Dim dsOriginal As DataSet = CreateOriginalDataset()

'dsWorking is the client copy
'starts off with original copy
Dim dsWorking As DataSet = dsOriginal.Copy
'client copy is modified
WorkWithDataset(dsWorking)

'get changes to send to the server<===
Dim dsChanges As DataSet = dsWorking.GetChanges

'persist out the changes (remoting process) <===
dsChanges.WriteXml("myFileName.xml", XmlWriteMode.DiffGram)

'read back in the changes (remoting processs) <===
Dim dsRead As New DataSet
CreateSchema(dsRead)
dsRead.ReadXml("myFileName.xml", XmlReadMode.DiffGram)

'merge client side changes to the server copy
dsOriginal.Merge(dsRead.GetChanges)
dsOriginal.AcceptChanges()

Console.WriteLine("")
Console.WriteLine("****
ORIGINAL*****************************************************")
Console.WriteLine("")
dsOriginal.WriteXml(Console.Out)

Console.WriteLine("")
Console.WriteLine("**** WORKING
*****************************************************")
Console.WriteLine("")
dsWorking.WriteXml(Console.Out)
Console.ReadLine()
End Sub
 
A

ABad

I don't think you are getting what I'm trying to show you. Take a step back
and look at what your example is doing at the *client only* for second.

Original state:
- Father1 in Father Table

Changes done by your example:
- Delete Father1 from Father Table
- Add Father1 to Father Table
- Add Child1 to Child Table with relationship to Father1 in Father Table

The client side XML changes that are being sent to the server are:
Before Snapshot:
- Father1 in Father Table
Proposed Changes
- Insert Father1 to Father Table
- Add Child1 to Child Table with relationship to Father1 in Father
Table

Do you see the problem in the client side XML changes that you sending to
the server? Look closely. You are telling the server that Father1 already
exists but *insert* another Father1 with a new Child1. The server doesn't do
the proposed changes of insert Father1 and Child1 because there is a unique
constraint on the Father table, and you are telling it to insert a duplicate
record. The result is the server only shows the Before Snapshot. My example
where the events are attached show this is exactly what is happening.

If you look at the XML changes from my final example you will see:
Before Snapshot:
- Father1 in Father Table
Proposed Changes
- Modified Father1 to Father Table
- Add Child1 to Child Table with relationship to Father1 in Father
Table

Do you see the difference? These XML changes do not try to insert another
Father1 record but modifies it. This is why my example works and this is
what your final XML changes should have when sent to the server.

- ABad
 
A

ABad

In other words change you client side logic From:
- Delete Father1 from Father Table
- Add Father1 to Father Table
- Add Child1 to Child Table with relationship to Father1 in Father
Table


To:
- Delete Father1 from Father Table
- Undo Father1 delete or rollback
- Add Child1 to Child Table with relationship to Father1 in Father
Table
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top