--------------------------------------------------------------------------------------
CopyrightŠ 2001, Software Structures, Inc. All Rights
Reserved.
--------------------------------------------------------------------------------------
---------------------------------
Note: This material assumes that on your machine you have installed Windows 2000, Visual Studio 6 (with Service Pack 4), Visual Studio .NET 7 Beta 1, .NET Framework SDK Beta 1 and SQL Server 2000.
---------------------------------
Summary
We extend Microsoft's .NET Framework SDK "QuickStart | How do I...? | XML Serialization | Read and Write objects into XML?" procedure for reading from XML data files. Our technique will serialize into a C# class instance any one record from a multi-record XML stream irrespective of the record's XML node depth.
The
Scenario.
Our goal is to serialize the XML representation of a data record into an instance of a C# (or VB) class. As outlined by the QuickStart Tutorials that come with Microsoft's .NET Framework SDK, the following ADO.NET based method may be used.
1. We start from a SQL Server 2000 table creatable by the SQL statement:
CREATE TABLE [dbo].[TestTable] (
[m_int] [int] NULL
) ON [PRIMARY]
GO
With 3 records: Column m_int
111
222
333
2. From a C# executable use ADO.NET class SQLConnection to open a connection to the SQL Server database.
3. Using an instance of ADO.NET class SQLDataSetCommand read the TestTable
records into an instance of ADO.NET class DataSet.
4. Use
DataSet.XmlSchema to extract as string the TestTable XSD schema and write it to
file TestTable.xsd. The generated
schema is:
<xsd:schema
id="NewDataSet" targetNamespace="" xmlns=""
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="TestTable">
<xsd:complexType content="elementOnly">
<xsd:all>
<xsd:element name="m_int"
minOccurs="0" type="xsd:int"/>
</xsd:all>
</xsd:complexType>
</xsd:element>
<xsd:element name="NewDataSet"
msdata:IsDataSet="True">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element ref="TestTable"/>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
5. Use
DataSet.WriteXmlData() to generate file TestTable.xml, an XML representation of
the TestTable data records. That XML
file is:
<?xml version="1.0"
standalone="yes"?>
<NewDataSet>
<TestTable>
<m_int>111</m_int>
</TestTable>
<TestTable>
<m_int>222</m_int>
</TestTable>
<TestTable>
<m_int>333</m_int>
</TestTable>
</NewDataSet>
6. Using the .NET tool Xsd.exe generate a C#
class representation of a TestTable record.
The Xsd.exe command is:
xsd.exe -classes -language:c#
-namespace: -outputdir:. TestTable.xsd
The
generated TestTable.cs is:
using System.Xml.Serialization;
[System.Xml.Serialization.XmlRootAttribute(Namespace="",
IsNullable=false)]
public class TestTable {
[System.Xml.Serialization.XmlElementAttribute("m_int",
Form=System.Xml.Serialization.XmlForm.Unqualified, IsNullable=false)]
public int M_int;
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool M_intSpecified;
}
7a. The
C# QuickStart code to do the actual serialization into an instance of class
TestTable (Item 6 above) is:
XmlSerializer
serializer= new XmlSerializer(typeof(TestTable));
TextReader
reader= new StreamReader("TestTable.xml");
TestTable
refTestTable= (TestTable)serializer.Deserialize(reader); // error
reader.Close();
The
Problem.
Under .NET Beta 1 an exception is thrown at the error line in Item 7a above. The exception message complains about the "unexpected" <NewDataSet> tag in TestTable.xml (Item 5 above). If one removes or comments the <NewDataSet> tag and re-executes the Item 7a code, the error does not occur and the TestTable instance is correctly initialized with data from the first XML record (111). Note, however, that removal of the <NewDataSet> tag renders TestTable.xml an invalid XML file since it lacks a single <Root> node.
A Solution.
7b. We
modify QuickStart's Item 7a code as follows:
XmlSerializer serializer= new
XmlSerializer(typeof(TestTable));
XmlElementExtractStream
refMyFileStream=
new XmlElementExtractStream(
"TestTable.xml" // string XmlFileToRead
,"TestTable" // string NameOfElementToExtractToStream
,0 // int
OccurrenceNdxOfElementToExtract
);
TextReader reader= new StreamReader(refMyFileStream);
TestTable refTestTable=
(TestTable)serializer.Deserialize(reader);
// ok
reader.Close();
8. Class
XmlElementExtractStream subclasses .NET's System.IO.FileStream and supports
serialization into a suitable (auto-generated) C# class instance of the data
record name-tagged with the value of the NameOfElementToExtractToStream
parameter. The OccurrenceNdxOfElementToExtract parameter determines which one
record is selected from a multi-record XML stream.
9. The
following code implements our C# class XmlElementExtractStream:
// XmlElementExtractStream.cs begin
namespace Sx1NetXMLSupportLib0CSharp
{
using System;
using System.Diagnostics;
public class
XmlElementExtractStream : System.IO.FileStream
{
private string
m_XmlFileToRead;
private string
m_NameOfElementToExtractToStream;
private int
m_OccurrenceNdxOfElementToExtract;
private int
m_FileSize;
private byte[]
m_FileByteImage;
private bool
m_bDidReadEntireFile;
private bool
m_bDidLocateElementOccurrence;
private int
m_nFileByteOffsetOfElementOccurrence;
private int
m_nFileByteCountSizeOfEntireElementSubstring;
private int
m_nOnReadOffsetOfNextFileByteToReturn;
private int
m_nOnReadByteCountRemaining;
public XmlElementExtractStream(
string XmlFileToRead
,string NameOfElementToExtractToStream
,int
OccurrenceNdxOfElementToExtract
) : base(
XmlFileToRead
,System.IO.FileMode.Open
,System.IO.FileAccess.Read
,System.IO.FileShare.ReadWrite
)
{
m_XmlFileToRead =
XmlFileToRead;
m_NameOfElementToExtractToStream = NameOfElementToExtractToStream;
m_OccurrenceNdxOfElementToExtract = OccurrenceNdxOfElementToExtract;
m_FileSize = (int)base.Length;
Debug.Assert(0
< m_FileSize);
m_bDidReadEntireFile = false;
m_bDidLocateElementOccurrence = false;
m_nFileByteOffsetOfElementOccurrence = -1;
m_nFileByteCountSizeOfEntireElementSubstring = 0;
m_nOnReadOffsetOfNextFileByteToReturn = -1;
m_nOnReadByteCountRemaining = 0;
} // end
XmlElementExtractStream()
override public int Read(
byte[] buffer
,int offsetInBufferAtWhichToWrite
,int nMaxCountBytesToWrite
)
{
int nRetVal = 0;
// default init
Debug.Assert(0
< nMaxCountBytesToWrite);
Debug.Assert(0
<= offsetInBufferAtWhichToWrite);
if(!m_bDidReadEntireFile)
{
m_bDidReadEntireFile = true; // => in this
'if' block only once
m_FileByteImage
= new byte[m_FileSize];
int nCountRead= base.Read(m_FileByteImage,
0, m_FileSize);
Debug.Assert(nCountRead == m_FileSize);
}
if(!m_bDidLocateElementOccurrence)
{
m_bDidLocateElementOccurrence = true; // => in this
'if' only once
//locate element occurrence
m_nFileByteOffsetOfElementOccurrence
=
FindFileByteOffsetOfElementOccurrence(
ref m_FileByteImage
,m_NameOfElementToExtractToStream
,m_OccurrenceNdxOfElementToExtract
,ref m_nFileByteCountSizeOfEntireElementSubstring
);
m_nOnReadOffsetOfNextFileByteToReturn =
m_nFileByteOffsetOfElementOccurrence;
m_nOnReadByteCountRemaining =
m_nFileByteCountSizeOfEntireElementSubstring;
}
if(0 <= m_nFileByteOffsetOfElementOccurrence
&&
0 <
m_nFileByteCountSizeOfEntireElementSubstring)
{
//respond to this current Read() request
int nByteCountToReturn= Math.Min(
nMaxCountBytesToWrite, m_nOnReadByteCountRemaining);
if(0 > nByteCountToReturn)
{
nByteCountToReturn
= 0;
}
if(0 < nByteCountToReturn)
{
for(int jjj= 0 ; jjj
< nByteCountToReturn ; ++jjj)
{
buffer[offsetInBufferAtWhichToWrite + jjj] =
m_FileByteImage[
jjj
+ m_nOnReadOffsetOfNextFileByteToReturn];
}
nRetVal =
nByteCountToReturn;
m_nOnReadOffsetOfNextFileByteToReturn +=
nByteCountToReturn;
m_nOnReadByteCountRemaining -=
nByteCountToReturn;
}
}
return nRetVal;
} // end
Read(byte[], int, int)
private int
FindFileByteOffsetOfElementOccurrence(
ref byte[]
refFileByteImage
,string ElementName
,int nNdxOffsetOfElementOccurrence
,ref int
refnByteCountSizeOfEntireElementSubstring
)
{
int nRetVal= -1;
// default init
refnByteCountSizeOfEntireElementSubstring = -1; // default init
Debug.Assert(0
<= nNdxOffsetOfElementOccurrence);
int tempFileByteOffsetOfElementOccurrence= 0;
int cumulativeNdxOffsetOfElementOccurrence= -1;
string FileStringImage;
unsafe
{
fixed(byte* pFileByteImage
= refFileByteImage)
{
// to see the value with
the debugger
fixed(sbyte*
pFileSByteImage= (sbyte*)pFileByteImage)
{
Debug.Assert(null != pFileSByteImage
&& 0 != *pFileSByteImage);
FileStringImage
= new string(pFileSByteImage);
}
}
}
string SubStrToFind= "<";
SubStrToFind +=
ElementName;
SubStrToFind +=
">";
int nPos0, nPos1;
nPos0 = 0;
while(true)
{
nPos0 = 1 +
FileStringImage.IndexOf(SubStrToFind, nPos0);
if(nPos0 <= 0)
{
// here, => still looking and didnt find it
goto LblReturnToCaller;
}
tempFileByteOffsetOfElementOccurrence =
nPos0 - 1;
cumulativeNdxOffsetOfElementOccurrence += 1;
if(cumulativeNdxOffsetOfElementOccurrence ==
nNdxOffsetOfElementOccurrence)
{
break; // while()
}
}
if(cumulativeNdxOffsetOfElementOccurrence ==
nNdxOffsetOfElementOccurrence)
{
// found it, now need length
SubStrToFind=
"</";
SubStrToFind +=
ElementName;
SubStrToFind +=
">";
nPos1 = 1 +
FileStringImage.IndexOf(SubStrToFind, nPos0);
if(nPos1 <= 0)
{
goto LblReturnToCaller;
}
nRetVal =
tempFileByteOffsetOfElementOccurrence;
refnByteCountSizeOfEntireElementSubstring
=
nPos1 -
nPos0 + 1 + SubStrToFind.Length - 1;
// debugging
//string str11;
//str11 = FileStringImage.Substring(
//
tempFileByteOffsetOfElementOccurrence,
//
refnByteCountSizeOfEntireElementSubstring
// );
//int dummy=0;
}
LblReturnToCaller:
return nRetVal;
} // end
FindFileByteOffsetOfElementOccurrence()
}
}
// XmlElementExtractStream.cs end
Done.
Rafael Pena
rpenaphd@worldnet.att.net
4/01/2001
Keywords: HowTo, How To