Creating Custom Xmlreader and Xmlwriter Classes
Total Page:16
File Type:pdf, Size:1020Kb
APPENDIX A ■ ■ ■ Creating Custom XmlReader and XmlWriter Classes In Chapter 3, you learned about the XmlReader and XmlWriter classes. The abstract classes XmlReader and XmlWriter can be used in three ways: •To call the Create() method of the respective classes that returns an instance of the generic XmlReader or XmlWriter classes • To use the concrete classes XmlTextReader and XmlTextWriter provided by the .NET Framework • To create custom classes that inherit from the XmlReader and XmlWriter classes You are already familiar with the first two approaches. In the following sections, you are going to learn how to create custom readers and writers from the abstract base classes XmlReader and XmlWriter. Creating a Custom Implementation of XmlReader In this section, you will create a custom implementation of the XmlReader class. The SqlCommand class provides the ExecuteXmlReader() method that returns an instance of XmlReader to the caller. This works fine if your database is SQL Server, but what if your database is Microsoft Office Access or any other OLEDB-compliant database? Moreover, XML extensions such as the FOR XML clause may not be available for all databases. Does that mean that you cannot retrieve the data and read it by using an XmlReader? Of course not. There is no out-of-the-box solution for this problem, but you can build your own mecha- nism to overcome this limitation, by creating a custom class that inherits from the XmlReader abstract class. You can then override the required properties and methods as per your need. The requirements for the custom XmlReader class are summarized here: • It should accept the database connection string and table name to read. • The column values should be treated as attribute values. • It should allow iterating through the table to read each row. • The column values should be accessible by specifying a column index or name. 457 458 APPENDIX A ■ CREATING CUSTOM XMLREADER AND XMLWRITER CLASSES Inheriting from XmlReader The XmlReader class is an abstract class and provides several properties and methods that you need to override when you inherit from it. Listing A-1 shows signatures of these properties and methods. Listing A-1. Properties and Methods of the XmlReader Class public abstract int AttributeCount; public abstract string BaseURI { get; } public abstract void Close(); public abstract int Depth { get; } public abstract bool EOF { get; } public abstract string GetAttribute(int i); public abstract string GetAttribute(string name, string namespaceURI); public abstract string GetAttribute(string name); public abstract bool HasValue { get; } public abstract bool IsEmptyElement { get; } public abstract string LocalName { get; } public abstract string LookupNamespace(string prefix); public abstract bool MoveToAttribute(string name, string ns); public abstract bool MoveToAttribute(string name); public abstract bool MoveToElement(); public abstract bool MoveToFirstAttribute(); public abstract bool MoveToNextAttribute(); public abstract XmlNameTable NameTable { get; } APPENDIX A ■ CREATING CUSTOM XMLREADER AND XMLWRITER CLASSES 459 public abstract string NamespaceURI { get; } public abstract XmlNodeType NodeType { get; } public abstract string Prefix { get; } public abstract bool Read(); public abstract bool ReadAttributeValue(); public abstract ReadState ReadState { get; } public abstract void ResolveEntity(); public abstract string Value { get; } You can override these properties and methods and write your own data-manipulation logic. If you do not want to override a particular property or method, you still need to have its empty implementation. A better way is to throw an exception in such properties and methods so that the caller knows that these properties and methods are not implemented by you. I will not discuss every property here because you are already familiar with many of them (see Chapter 3 for more information). Creating a TableReader Class Now that you are familiar with the XmlReader abstract class, let’s create our own implementa- tion. To do so, create a new project of type class library by using Visual Studio. Add a class named TableReader. Make sure that references to the System.Xml and System.Data assemblies are added to the project. Import the namespaces as shown in Listing A-2 at the top of the TableReader class and ensure that the TableReader class inherits from the XmlReader class. Listing A-2. Importing Namespaces and Setting Inheritence using System.Xml; using System.Data; using System.Data.OleDb; class TableReader:XmlReader { ... 460 APPENDIX A ■ CREATING CUSTOM XMLREADER AND XMLWRITER CLASSES You need to add an implementation of each property and method mentioned. Visual Studio provides a shortcut for adding empty implementations of these members. Right-click on the XmlReader class in the class definition and choose the Implement Abstract Class menu option (Figure A-1). Figure A-1. Adding empty implementations of properties and methods This will add dummy signatures of all the properties and methods that need to be overrid- den. Notice how the dummy implementation throws an exception by using the throw keyword. This way, if somebody tries to use unimplemented members, an exception will be thrown indi- cating that “the method or operation is not implemented.” Code the TableReader class as shown in Listing A-3. Listing A-3. The TableReader Class public class TableReader:XmlReader { private OleDbConnection cnn; private OleDbCommand cmd; private OleDbDataReader reader; private int intColumnIndex = -1; private string strValue; public TableReader(string connectionString,string tableName) { cnn = new OleDbConnection(connectionString); cmd = new OleDbCommand(); cmd.Connection = cnn; cmd.CommandText = tableName; cmd.CommandType = CommandType.TableDirect; cnn.Open(); reader = cmd.ExecuteReader(); } APPENDIX A ■ CREATING CUSTOM XMLREADER AND XMLWRITER CLASSES 461 public override int AttributeCount { get { return reader.FieldCount; } } public override void Close() { reader.Close(); cnn.Close(); } public override int Depth { get { return reader.Depth; } } public override string GetAttribute(int i) { return reader.GetValue(i).ToString(); } public override string GetAttribute(string name) { return reader.GetValue(reader.GetOrdinal(name)).ToString(); } public override bool MoveToAttribute(string name) { intColumnIndex = reader.GetOrdinal(name); return true; } public override bool MoveToElement() { intColumnIndex = -1; return true; } 462 APPENDIX A ■ CREATING CUSTOM XMLREADER AND XMLWRITER CLASSES public override bool MoveToFirstAttribute() { intColumnIndex = 0; return true; } public override bool MoveToNextAttribute() { intColumnIndex++; if (intColumnIndex > reader.FieldCount - 1) { return false; } else { return true; } } public override bool Read() { intColumnIndex = -1; strValue = ""; return reader.Read(); } public override bool HasValue { get { return reader.IsDBNull(intColumnIndex); } } public override bool ReadAttributeValue() { if (intColumnIndex < reader.FieldCount) { strValue = reader.GetValue(intColumnIndex).ToString(); return true; } else { return false; } } APPENDIX A ■ CREATING CUSTOM XMLREADER AND XMLWRITER CLASSES 463 public string Name { get { if (intColumnIndex == -1) { return cmd.CommandText; } else { return reader.GetName(intColumnIndex); } } } public override string Value { get { return strValue; } } ... } In the following text, we will dissect the code step by step. Declaring Class-Level Variables private OleDbConnection cnn; private OleDbCommand cmd; private OleDbDataReader reader; private int intColumnIndex = -1; private string strValue; The TableReader class declares private variables of type OleDbConnection, OleDbCommand, and OleDbDataReader classes at the class level: •The OleDbConnection class is used to establish a connection with OLEDB-compliant databases such as Access. •The OleDbCommand class is used to execute any query, SQL query, or stored procedures against a database. •The OleDbDataReader class allows you to iterate through a result set in a cursor-oriented manner. The intColumnIndex integer variable keeps track of the current column index whose value is to be read. Similarly, the strValue string variable stores the value from the column indicated by intColumnIndex. 464 APPENDIX A ■ CREATING CUSTOM XMLREADER AND XMLWRITER CLASSES Initializing the Variables public TableReader(string connectionString,string tableName) { cnn = new OleDbConnection(connectionString); cmd = new OleDbCommand(); cmd.Connection = cnn; cmd.CommandText = tableName; cmd.CommandType = CommandType.TableDirect; cnn.Open(); reader = cmd.ExecuteReader(); } The constructor of the TableReader class accepts two parameters: the database connec- tion string and the name of the table whose data is to be read. Using the connection string, the OleDbConnection is instantiated. The Connection property of the OleDbCommand class is set to the OleDbConnection class we just instantiated. The CommandText property of the OleDbCommand class is set to the name of the table whose data is to be read. Have a look at the CommandType property. It is set to TableDirect, which returns all the rows from the table indicated by the CommandText property. In effect, it works as if we have specified SELECT * FROM <tableName> as the query. The database connection is then opened. The ExecuteReader() method of OleDbCommand is called and an OleDbDataReader is retrieved. Retrieving the Total Number of Attributes public override int AttributeCount { get { return reader.FieldCount; } } The TableReader class