To fully demonstrate the workings of XML DiffGrams, nothing is better than taking a DataSet object, entering some changes, and seeing how the corresponding DiffGram representation varies. For this purpose, I created the DiffGram Viewer Windows Forms application, shown in Figure 10-3. The application is available in this book’s sample files.
This application executes a couple of SQL commands to obtain a DataSet object filled with two tables—Employees and Territories. The names of the DataSet object and the in-memory tables can be changed at will using text boxes. Next the application creates a relation between the tables, sets the nesting property to true, and creates the DiffGram.
The DiffGram is created using an in-memory string writer, and the output text is written to a multiline, read-only text box. Clicking the Edit button opens a new form with a DataGrid control for editing rows. The DataGrid control is bound to the DataSet object generated by the query, and is shown in Figure 10-4.
The child form allows you to set errors and enter any type of changes. When the form is dismissed, the main application automatically saves the bound DataSet object back to a DiffGram and refreshes the user interface. As a result, you can easily test the DiffGram and view how the output varies after data changes.
A nice feature of the DiffGram Viewer application is that it lets you toggle the DiffGram view between plain text and XML. The XML view is provided by Internet Explorer, as shown in Figure 10-5.
The DiffGram Viewer application makes use of the WebBrowser ActiveX control, which is imported almost seamlessly by Microsoft Visual Studio .NET. The following code shows how to refresh such a Web view. To view the DiffGram using the WebBrowser control, the DiffGram must first be saved to disk as a temporary XML file.
void RefreshWebBrowser() { // Url is a form property that points to the DiffGram file object o1=null, o2=null, o3=null, o4=null; WebBrowser.Navigate(Url, ref o1, ref o2, ref o3, ref o4); }
A DiffGram has no trace of relationships between tables unless the Nested property of the DataRelation object is set to true. This system is reasonable in light of what we saw in Chapter 9. ADO.NET serializes information about tables relationships using XML Schema constructs. Because a DiffGram does not include schemas, it can’t contain static information about table relationships. When the Nested property is set to true, the parent/child relationship is expressed by grouping child rows as a subtree of the parent row.
A DiffGram is programmatically created by calling the WriteXml method of the DataSet class. To save data to a DiffGram, however, you must explicitly set the XmlWriteMode argument of the method to the flag XmlWriteMode.DiffGram, as shown in the following code. The XML data created in this way does not include schema information. We’ll return to this important point in the section “Schema Information in the DiffGram,” on page 461.
// Prepare the output stream StreamWriter sw = new StreamWriter(fileName); XmlTextWriter writer = new XmlTextWriter(sw); writer.Formatting = Formatting.Indented; // Create the diffgram ds.WriteXml(writer, XmlWriteMode.DiffGram); writer.Close();
The DiffGram contains all the rows from all the tables found in the DataSet object. You can create ad hoc subsets of the DataSet object to narrow the information being saved. In particular, you can use the DataSet object’s GetChanges method to save only those rows that contain uncommitted changes, as shown here:
DataSet dsChanges = ds.GetChanges(); dsChanges.WriteXml(writer, XmlWriteMode.DiffGram);
The GetChanges method also has a few overloads that let you control the type of changes you are interested in. For example, the following code prepares a DiffGram containing only the rows that have been inserted:
DataSet dsChanges = ds.GetChanges(DataRowState.Added); dsChanges.WriteXml(writer, XmlWriteMode.DiffGram);
When you try to build a DataSet object from an XML DiffGram, you must first ensure that the target DataSet object has a schema that is compatible with the data in the DiffGram.
In no case does the ReadXml method—the only DataSet method that can load a DiffGram—infer the schema or extend with new elements an existing schema. ReadXml works by merging the rows read from the DiffGram with existing rows in the DataSet object. The DiffGram row identifier (the diffgr:id attribute) is used to pair DiffGram and DataSet object rows.
Any incompatibility between the current schema of the DataSet object and the data in the DiffGram throws an exception and causes the merge operation to fail. As a result, you can’t load a DiffGram into an empty, newly created DataSet object. You can create the target DataSet object simply by cloning an existing object that you know has the correct schema. Or, more realistically, you might want to read the schema from an external support using the ReadXmlSchema method. The following code snippet shows how to create a DiffGram and its schema in distinct files:.
// Prepare the output stream for the DiffGram StreamWriter diffStrm = new StreamWriter(diffgramFile); XmlTextWriter writer = new XmlTextWriter(diffStrm); writer.Formatting = Formatting.Indented; // Create the diffgram from the ds DataSet ds.WriteXml(writer, XmlWriteMode.DiffGram); writer.Close(); // Prepare the output stream for the schema StreamWriter xsdStrm = new StreamWriter(schemaFile); XmlTextWriter writer = new XmlTextWriter(xsdStrm); writer.Formatting = Formatting.Indented; // Create the schema from the ds DataSet ds.WriteXmlSchema(writer); writer.Close();
The schema written with WriteXmlSchema is an XML Schema and includes table, relation, and constraint definitions.
In general, the schema and the data should be kept in separate files and handled as truly independent entities. The schema and the data are tightly coupled, and if serialization is involved, you might want to consider putting schema information in-line in the data.
In the .NET Framework, the WriteXml method does not provide the capability to include schema information along with the data. This is more of a design choice than an objective difficulty. An indirect confirmation comes from the XML string you get from a Web service method that returns a DataSet object. The output is a DiffGram extended with schema information, as shown here:
<DataSet> <xs:schema> ... </xs:schema> <diffgr:diffgram ... > ... </diffgr:diffgram> </DataSet>
By design, the current DiffGram implementation does not include schema information. However, I can’t see any reason for not providing the schema option in future versions. The DataSet representation you get from a Web service method offers a glimpse of what could be a possible enhancement of the DiffGram format. Technically speaking, the Web service serialization of a DataSet object is not a DiffGram, but rather a new XML format that incorporates a DiffGram. In addition, this new format is not produced by WriteXml but comes care of the XML serializer—a different breed of data formatter that we’ll explore in Chapter 11.
The DiffGram Viewer application includes a Save With Schema check box that enables you to persist the DataSet object using the XML serializer. The final output, shown in Figure 10-6, is the same as you would obtain through a Web service. (This happens because .NET Framework Web services are actually serviced by the XML serializer.)
The code that saves the DataSet object to a DiffGram changes as follows:
StreamWriter sw = new StreamWriter(fileName);
XmlTextWriter writer = new XmlTextWriter(sw);
writer.Formatting = Formatting.Indented;
// Create the diffgram
if (!bUseSchema)
ds.WriteXml(writer, XmlWriteMode.DiffGram);
else
{
XmlSerializer ser = new XmlSerializer(typeof(DataSet));
ser.Serialize(writer, ds);
}
writer.Close();
If schema information must be included, the application makes use of the XML serializer defined in the System.Xml.Serialization namespace. The constructor of the XML serializer takes the type of the data to process as an argument and invokes the Serialize method. In Chapter 11, I’ll unveil what really happens at this stage and how the XML serializer sets itself up to work on a particular data type. For now, suffice to say that once the instance of the serializer has been configured, you simply call the Serialize method on the object instance to be persisted. When the object is a DataSet, the output is a DiffGram and a schema—that is, an XML Schema and a DiffGram rooted under a common node. The name of the root matches the name of the type being serialized (for example, DataSet) and can’t be modified programmatically.
To read back a DiffGram and a schema into a DataSet object, you call the XML deserializer. Deserialization is the process of reading an XML document and building an object instance that coincides with a given XML Schema. With DataSet objects, the schema and the data are stored as distinct nodes under a common root. The data is expressed as a DiffGram.
To set up the serializer, follow the same steps as in the previous section. You instantiate the XmlSerializer class and pass the type of the object to process, as shown here:
XmlSerializer ser = new XmlSerializer(typeof(DataSet)); DataSet dsNew = (DataSet) ser.Deserialize(writer, ds);
To deserialize, call the Deserialize method and cast the object you get to the DataSet type.
3.142.200.226