This article describes a mechanism that uses a Singleton Factory Class for creating DataSet objects that represent sets of views, locating the base configuration nodes for each view via XPath query. Each view represents the data of interest to a particular user role and consists of a set of tables, which are expressed in XML both as a data document and as a schema. The Factory Class is configured via an XML document. By dynamically adding nodes to it, the Factory Class can have its capabilities expanded at run time.
Imagine a Web application that provides a point-of-presence for many franchisees. Each franchisee has what amounts to his or her own Web site, and the franchiser controls the format and content. In this scenario, there are many kinds of users (customers, franchisees, central office administrators, compliance officers), each of whom needs her own set of data. This demand, of course, is a big reason for using a DataSet. DataSets are designed to represent a specific view into the data layer. Minimizing coupling between architectural layers suggests that the layer above the data access layer should know as little as possible about data storage. Deriving specialized subclasses of DataSet is one mechanism for doing this, but it propagates that specialized class into the upper layers. The approach I describe in this article provides the upper layers with a string key (in this example, the key is the role name) that is used in an XPath query to locate a node in a configuration document. From that node, a generic DataSet is outfitted and filled from the appropriate data source and returned to the caller. The data access layer interfaces to the layers above it via generic DataSets, and the customization of those DataSets is specified in an external XML configuration document.
Now imagine adding a module that needs a different sort of data added to the application. While it’s not hard to edit an existing XML document, it turns out that it’s quite easy to add a node to the DataSetFactory configuration document that’s already loaded so that it can generate a new kind of DataSet.
Listing A shows the source code for the DataSetFactory class. There are three noteworthy aspects of this class.
First, the class employs the Singleton design pattern. This ensures that there is only one instance of the class in existence in a given AppDomain at a given time. This pattern is typical for factories. It’s useful for resource conservation, and since multiple threads can access the GetDataSet(…) method, it can be made to scale—though not necessarily exactly as written. With the Singleton design pattern, the user of a class is typically prevented from explicitly instantiating an object of that type himself. This is accomplished by making the constructor private. A would-be user of an instance of the type is forced to acquire the instance through (in this case) a class static-property getter of type DataSetFactory. The getter method first determines whether there is a single instance already created. If there is, it returns that instance to the caller. If an instance has not yet been created, one is created, and the class passes back a reference to that instance, keeping a copy for itself, to service future requests.
Notice that the instance-property getter is synchronized. While outside of the scope of this article, this is important. An esoteric set of arguments and a rather intricate construct can be used to avoid the need to synchronize the getter, but unless you understand the issues intimately, you should synchronize the getter.
Upon the creation of the Singleton instance, the DataSetFactory loads an XML document that represents the factory’s known DataSet configurations. This document is shown in Listing B.
The second aspect of this class is the GetDataSetMethod(string whichOne), which populates one or more tables in a DataSet according to commands and table names specified in the configuration document. In this method, the factory creates an XPath query that locates the configuration node for the specified DataSet. The query string in the listing says to go find the node that is off the root through a DataSets node that has the name attribute and that matches the DataSet specifier contained in the string variable whichOne. If there is no such node, null is returned for the DataSet. The latter two-thirds of the GetDataSet(…) method then proceeds to create and fill a table in the DataSet from each Query node with the specified query string and table name. If you look carefully, you’ll see that the method used to fill the DataSet only fills five rows. The code needed to fill it in its entirety is commented out, in the same line. Use it if you dare—there are a lot of rows.
The third aspect of this factory is the AddDataSetConfig(XmlNode newDataSet) method, which allows you to add DataSet configuration nodes to the configuration document, and thereafter, to request DataSets built upon that specification. All it does is use the XmlDocument.ImportNode(XmlNode) API to import the XmlNode provided by the caller, and then use the XmlNode.AppendChild(XmlNode) API to add a that node to the configuration document. After doing so, the new node can be selected in the same manner as the other, predefined nodes, to create a new kind of DataSet.
Listing C shows the source code for the Runner class. This class acts as an exerciser for the DataSetFactory. In the first line, I obtain the DataSetFactory instance and get a DataSet configured as a Sales DataSet. The tables and queries in this DataSet are described in the configuration document in Listing B. I then write out an XML schema for the DataSet and an XML data document with its contents.
After creating and writing out an XML schema and document for a predefined DataSet, I create an XML document that contains a specification for a new type of DataSet, a ClubMember DataSet. I then pass the node that contains that specification into the DataSetFactory’s AddDataSetConfig(XmlNode) method, where the DataSetFactory is outfitted to produce a new type of DataSet. Finally, I request a DataSet of the new type.
Grab the code
You can download the code here. You need to have SQL Server 2000 installed and running, as well as the Northwind database.
This article outlines a mechanism for flexibly and dynamically creating datasets from an XML configuration document. This mechanism also allows you to create and add new such configurations on the fly. I’ve touched on ways to write a DataSet’s XML schema out to a stream, as well as an XML data document containing the DataSet’s contents, and demonstrated one way to use XPath to navigate a document.
While this mechanism is rather rudimentary and would need minor improvements in order to be useful in a production environment, I hope it shows a useful technique and sparks some ideas for answers to your challenges.