Chapter 7Saving and Restoring Data After completing this chapter, you will be able to: ■ ■ Export a DataSet to a file in XML format ■ ■ Import a previously exported DataSet from XML form
Trang 1Chapter 7
Saving and Restoring Data
After completing this chapter, you will be able to:
■
■ Export a DataSet to a file in XML format
■
■ Import a previously exported DataSet from XML format
■
■ Define the structure of the exported XML content
■
■ Access the XSD schema for a DataSet or DataTable
ADO.NET isn’t the only popular format for managing data in NET applications XML—content crafted using the Extensible Markup Language—is another common format that provides standardized, cross-platform data management in a semi-human-readable format
The DataSet class and the DataTable instances contained within it include features for moving
data back and forth between ADO.NET and XML This chapter demonstrates those features,
focusing on the ability to serialize the contents of a DataSet for later use, either by loading
it into another DataSet or by accessing the data directly through some other XML-enabled
application ADO.NET includes full schema definition support using Schema Definition Language (XSD)
Note Before version 4, ADO.NET included an XmlDataDocument class that supported on-demand synchronization between the contents of a DataSet and an XML document That class has
since been deprecated You can simulate some of the functionality formerly available through
XmlDataDocument using the features discussed in this chapter You can also use DataSet–focused
LINQ queries, as discussed in Chapter 18, “Using LINQ to DataSet,” as a substitute for the
obso-lete XmlDataDocument class.
Serializing DataSet and DataTable Objects
ADO.NET was designed with XML in mind, so generating XML content from a DataSet takes very little effort Reading XML content into a DataSet is even easier because ADO.NET will
guess at the correct structure of the data even if you don’t provide table design guidance
Trang 2Writing XML
To generate XML for the data content of an existing DataSet instance, call its WriteXml method,
passing an output file name
C#
DataSet infoSet = new DataSet();
// - Add tables, relations, and data, then call
infoSet.WriteXml(@"c:\StorageFile.xml");
Visual Basic
Dim infoSet As New DataSet
' - Add tables, relations, and data, then call
infoSet.WriteXml("c:\StorageFile.xml")
In addition to file names, various overloads of WriteXml accept a valid Stream instance, a TextWriter instance, or an XmlWriter instance as their first argument The generated XML is
straightforward, using table and column names to define each element tag Here is some
typical XML data content produced by WriteXml This content includes three customer data rows, each with four fields: a string column (BusinessName), two numeric fields (ID, AnnualFee), and a date value (ContractDate) in UTC format with a time zone offset.
<CustomerDataSet>
<Customer>
<ID>1</ID>
<BusinessName>City Power & Light</BusinessName>
<AnnualFee>500</AnnualFee>
<ContractDate>2008-06-01T00:00:00-07:00</ContractDate>
</Customer>
<Customer>
<ID>2</ID>
<BusinessName>Lucerne Publishing</BusinessName>
<AnnualFee>300</AnnualFee>
<ContractDate>2008-01-01T00:00:00-08:00</ContractDate>
</Customer>
<Customer>
<ID>3</ID>
<BusinessName>Southridge Video</BusinessName>
<AnnualFee>350</AnnualFee>
<ContractDate>2010-02-15T00:00:00-08:00</ContractDate>
</Customer>
</CustomerDataSet>
Trang 3By default, WriteXml writes XML for only the data rows in each table; the method saves no information about the structure of the DataSet To include the DataSet object’s schema defi-nition along with the data, add a second argument to the WriteXml method call, passing XmlWriteMode.WriteSchema.
C#
infoSet.WriteXml(targetFile, XmlWriteMode.WriteSchema);
Visual Basic
infoSet.WriteXml(targetFile, XmlWriteMode.WriteSchema)
Other XmlWriteMode enumeration members include IgnoreSchema (don’t include the schema, which is the same as leaving off the second argument) and DiffGram (a special format that outputs differences between the Original and the Current versions of each DataRow within the DataSet).
If you want to output only the schema, use the DataSet object’s WriteXmlSchema method, passing it a file name, a Stream, a TextWriter, or an XmlWriter.
C#
infoSet.WriteXmlSchema(targetSchemaFile);
Visual Basic
infoSet.WriteXmlSchema(targetSchemaFile)
The DataTable class also includes WriteXml and WriteXmlSchema methods that you can use
to generate XML content on a table-by-table basis In addition to the file/stream/writer
tar-get and the XmlWriteMode arguments, the DataTable versions of these methods accept an optional Boolean argument that indicates whether child tables linked via DataRelation
ob-jects should be sent to the output with the instantiating table’s schema or data You can use
this Boolean argument either after or instead of the XmlWriteMode argument.
C#
// - Write the customer data AND the linked order data.
customers.WriteXml(targetFile, true);
Visual Basic
' - Write the customer data AND the linked order data.
customers.WriteXml(targetFile, True)
Trang 4If you want to keep the XML content in the application, the DataSet class includes GetXml and GetXmlSchema methods that return string documents with content similar to the output
of the WriteXml and WriteXmlSchema methods The DataTable.GetDataTableSchema method
returns the XSD for a table in plain string format
Reading XML
Both the DataSet and DataTable classes include ReadXml and ReadXmlSchema counterparts
to the XML-writing methods To use them, create a new DataSet or DataTable instance; then call the appropriate method, passing a file name, a Stream, a TextReader, or an XmlReader.
C#
DataSet infoSet = new DataSet();
// - To read the schema, use
infoSet.ReadXmlSchema(@"c:\StorageSchemaFile.xml");
// - To read the data, use
infoSet.ReadXml(@"c:\StorageFile.xml");
Visual Basic
Dim infoSet As New DataSet
' - To read the schema, use
infoSet.ReadXmlSchema("c:\StorageSchemaFile.xml")
' - To read the data, use
infoSet.ReadXml("c:\StorageFile.xml")
A second argument to the DataSet.ReadXml method lets you indicate how the incoming
content should be processed It uses one of the following enumerated values:
■
■ XmlReadMode.Auto Lets ReadXml figure out what to do with the incoming content
automatically If it detects a valid schema with the data, it processes the schema before
loading the data If it sees a DiffGram, it interprets it appropriately This is the default
option if you don’t add the read-mode argument
■
■ XmlReadMode.ReadSchema Reconstructs the DataTable members of the DataSet
without loading in the data
Trang 5■ XmlReadMode.IgnoreSchema Loads in the data, ignoring any schema that might be
included in the XML Instead, the existing DataSet structure is used.
■
■ XmlReadMode.InferSchema Builds a new schema based on the structure of the XML
data alone, ignoring any included schema If needed, any existing DataSet structure will
be augmented with new schema information
■
■ XmlReadMode.DiffGram Reads in the content previously written with the WriteXml
method’s XmlWriteMode.DiffGram mode.
■
■ XmlReadMode.Fragment Reads in and processes XML content that might be partial
or incomplete
■
■ XmlReadMode.InferTypedSchema Similar to the InferSchema mode, but ReadXml
will go out of its way to figure out the data type of each incoming data column
ReadXml or ReadXmlSchema support both inline and linked XSD structure definitions.
DataSet includes an additional InferXmlSchema method It works just like the ReadXmlSchema
method, but you can pass it an array of namespace names to exclude on import
Guiding XML Generation
The Read and Write XML methods generate valid XML that can be used right away with
any XML tools Still, the default format might be insufficient for your processing needs That’s why ADO.NET includes features that let you guide and enhance the XML generation process There are three main types of guidance you can provide to the XML content: namespace identification, child table nesting, and column management
Identifying Namespaces
XML includes a namespace feature that lets you group content by purpose, even among tags
that appear within the same parent element Three ADO.NET classes—DataSet, DataTable, and DataColumn—include properties that let you assign both the namespace and the
namespace prefix that will appear in the XML tags associated with the table and column values
Trang 6Each of these three classes includes a Namespace property, a string value containing the tar-get XML namespace name A second property, Prefix, defines the short prefix prepended to
tag names that belong to the namespace The following code sets the namespace and prefix
for a DataTable; the process for setting these values in a DataSet or DataColumn is identical:
C#
DataTable customers = new DataTable("Customer");
customers.Namespace = "corporate";
customers.Prefix = "co";
Visual Basic
Dim customers As New DataTable("Customer")
customers.Namespace = "corporate"
customers.Prefix = "co"
The addition of the namespace and the prefix modifies the generated XML to include the
necessary xmlns attributes and prefix components.
<co:Customer xmlns:co="corporate">
<ID xmlns="corporate">1</ID>
<BusinessName xmlns="corporate">City Power & Light</BusinessName>
.
Setting only the DataTable (or DataSet) namespace values applies the xmlns tag to each
contained column-related element To change these column entries to prefix-bearing tags
instead, set the Namespace and Prefix properties within each of the table’s DataColumn
objects
The constructor for the DataTable class also includes a parameter that sets the Namespace property during object creation Neither DataSet nor DataColumn includes such a parameter.
C#
DataTable customers = new DataTable("Customer", "corporate");
Visual Basic
Dim customers As New DataTable("Customer", "corporate")
The namespace and prefix settings are overridable Setting these values at the DataSet level
affects all tables within the data set except those that have their own distinct namespace
val-ues Setting the DataTable-level fields affects its columns unless you override it by setting the two properties in the DataColumn object.
Trang 7Nesting Child Tables
By default, each table within a DataSet has its rows output at the same element level In
a data set with Customer and Order tables, each row in the Customer table would appear within the data set’s top-level XML element, followed by each row in the Order table at the same level as the Customer records Sometimes it is better to have the child table records
that belong to a parent record physically appear within their parent XML element Sample
code earlier in this chapter showed how adding an extra argument to the DataTable.WriteXml method would accomplish this But when generating XML for an entire DataSet, you must indicate your desire to nest child tables by setting the Nested property in the relevant DataRelation object.
C#
DataRelation customerOrder = new DataRelation(
customers.Columns["ID"], orders.Columns["CustomerID"]);
customerOrder.Nested = true;
Visual Basic
Dim customerOrder As New DataRelation(
customers.Columns!ID, orders.Columns!CustomerID)
customerOrder.Nested = True
Managing and Positioning Columns
As ADO.NET outputs the XML content for a DataTable, it first generates a tag for each row in
the table, using the table’s name as the containing tag Within this row tag, each column gets its own tagged element The data for each column appears as text within the column element The following content shows a single row from the “Customer” table, with subordinate tag elements for each of the four columns in the row:
<Customer>
<ID>1</ID>
<BusinessName>City Power & Light</BusinessName>
<AnnualFee>500</AnnualFee>
<ContractDate>2008-06-01T00:00:00-07:00</ContractDate>
</Customer>
Sometimes you might want one or more columns to appear as attributes for the row-level tag instead
<Customer ID="1">
Trang 8This is accomplished by setting the DataColumn.ColumnMapping property for the relevant
column object This property can be set to one of four enumerated values:
■
■ MappingType.Element The column data appears within its own XML tag element
This is the default setting for all columns
■
■ MappingType.Attribute The column value is moved into the row’s tag and stored as
an XML attribute
■
■ MappingType.SimpleContent The data for this column becomes the entire content
for the row’s tag element Only one column within a table can be designated as the
SimpleContent column All other columns must either be set as attributes or must be
hidden
Note Setting a column’s mapping type to SimpleContent will generate an exception if any other columns in the same table have a mapping type of Element or SimpleContent.
■
■ MappingType.Hidden This column is excluded from the generated XML content.
In addition to setting the ColumnMapping property, the constructor for the DataColumn
ob-ject lets you define the mapping type
C#
DataColumn orderID = new DataColumn("ID", typeof(int), MappingType.Attribute);
Visual Basic
Dim orderID As New DataColumn("ID", GetType(Integer), MappingType.Attribute)
Generating XML from a DataSet: C#
1 Open the “Chapter 7 CSharp” project from the installed samples folder The project
in-cludes one Windows.Forms class named Serialization.
2 Open the source code view for the Serialization form Locate the ActGenerate_Click
function This routine produces the XML content from a sample DataSet containing two tables: Customer and Order.
3 Just after the “Set the XML namespace” comment, add the following statements:
SampleDataSet.Tables["Customer"].Namespace = TableNamespace.Text.Trim();
SampleDataSet.Tables["Customer"].Prefix = TablePrefix.Text.Trim();
SampleDataSet.Tables["Order"].Namespace = TableNamespace.Text.Trim();
SampleDataSet.Tables["Order"].Prefix = TablePrefix.Text.Trim();
This code sets the namespace and prefix values for both of the sample tables
Trang 9Note As mentioned in the chapter discussion, you can also define namespace and prefix
val-ues within each DataColumn Although it is not included in the sample code, consider adding
code that will loop through all columns in each of the two tables and add the user-specified namespace and prefix values.
4 Just after the “Indicate the relationship type” comment, add the following line:
SampleDataSet.Relations[0].Nested = NestChildRecords.Checked;
This statement determines whether the order rows for each customer record are
con-tained within the <Customer> tag (true) or whether all <Order> tags appear after and
at the same level as all the <Customer> tags in the XML (false).
5 Just after the “Build a memory stream to hold the results” comment, add the following
code:
holdBuffer = new MemoryStream(8192);
SampleDataSet.WriteXml(holdBuffer,
(XmlWriteMode)OutputWriteMode.SelectedItem);
These lines perform the actual XML generation, sending the results to a stream, in this
case a MemoryStream instance The remaining code in the event handler moves the
XML content from the stream to an on-form text box
6 Run the program Use the fields in the upper-right corner of the form to alter the XML
content and then click Generate to produce the XML As an example, set the XML Write
Mode to WriteSchema, change the Mapping for both Parent.ID and Child.ID to Attribute, and set the Mapping for Child.CustomerID to Hidden Click Generate The
XML will contain the XSD schema for the data set, followed by distinct <Customer> and
<Order> elements.
Trang 10Generating XML from a DataSet: Visual Basic
1 Open the “Chapter 7 VB” project from the installed samples folder The project includes
one Windows.Forms class named Serialization.
2 Open the source code view for the Serialization form Locate the ActGenerate_Click
function This routine produces the XML content from a sample DataSet containing two tables: Customer and Order.
3 Just after the “Set the XML namespace” comment, add the following statements:
SampleDataSet.Tables("Customer").Namespace = TableNamespace.Text.Trim
SampleDataSet.Tables("Customer").Prefix = TablePrefix.Text.Trim
SampleDataSet.Tables("Order").Namespace = TableNamespace.Text.Trim
SampleDataSet.Tables("Order").Prefix = TablePrefix.Text.Trim
This code sets the namespace and prefix values for both of the sample tables
Note As mentioned in the chapter discussion, you can also define namespace and prefix
val-ues within each DataColumn Although it is not included in the sample code, consider adding
code that will loop through all columns in each of the two tables and add the user-specified namespace and prefix values.
4 Just after the “Indicate the relationship type” comment, add the following line:
SampleDataSet.Relations(0).Nested = NestChildRecords.Checked
This statement determines whether the order rows for each customer record are
con-tained within the <Customer> tag (True) or whether all <Order> tags appear after and
at the same level as all the <Customer> tags in the XML (False).
5 Just after the “Build a memory stream to hold the results” comment, add the following
code:
holdBuffer = New MemoryStream(8192)
SampleDataSet.WriteXml(holdBuffer,
CType(OutputWriteMode.SelectedItem, XmlWriteMode))
These lines perform the actual XML generation, sending the results to a stream, in this
case a MemoryStream instance The remaining code in the event handler moves the
XML content from the stream to an on-form text box