- Adapting to an Interface
- Class and Object Adapters
- Adapting Data in .NET
- Summary
Adapting Data in .NET
If you search for “adapter” in the online help for Visual Studio .NET, you will find that almost all the results relate to adapting database data. This is not too surprising, because a major goal of an n-tier architecture is to define how data is represented in each tier: in persistent storage, in business objects, and in visual presentations. An architecture must also supply mechanisms for transforming data between these representations, as we will have frequent need to adapt data in one tier to the meet the needs of another tier.
Although the .NET Framework Class Libraries provide ample support for adapting data to the needs of different architectural layers, not all data adapters are examples of the ADAPTER pattern. It is useful to look at a data adapter example and then ask whether ADAPTER plays a role or could play a role. Using the .NET FCL, it is easy to create an adapter that can take a structured query language (SQL) query and extract database data. The Oozinoz DataServices class encapsulates this adaptation as a service as follows:
using System; using System.Data; using System.Data.OleDb; //... namespace DataLayer { public class DataServices { //... public static OleDbDataAdapter CreateAdapter (string select) { return new OleDbDataAdapter( select, CreateConnection()); } //... } }
The static CreateAdapter() method returns an “adapter” of type OleDbDataAdapter that contains the results of a SQL select statement. One of the most useful methods that the OleDbDataAdapter class supports is Fill(), a method that pushes database data from the adapter object into a DataSet object. An instance of the DataSet class is essentially an in-memory relational database (sans engine) that houses tables and their relationships. Several graphical control classes, such as the DataGrid class, can extract data from a DataSet object. The OldDbDataAdapter, DataSet, and DataGrid classes collaborate to make it easy to wire together applications that whisk data along from a database, through a dataset, and into a visual representation.
To see a data adapter in action at Oozinoz, we need first to take a quick look at the Oozinoz UI utility class. This class provides several standard graphical user interface (GUI) objects, including a standard font and a standard DataGrid object, as the following code shows:
using System; using System.Drawing; using System.Windows.Forms; namespace UserInterface { public class UI { public static readonly UI NORMAL = new UI(); protected Font _font = new Font("Book Antiqua", 18F); public virtual Font Font { get { return _font; } } // public virtual DataGrid CreateGrid() { DataGrid g = new DataGrid(); g.Dock = DockStyle.Fill; g.CaptionVisible = false; return g; } } }
Using the UI class along with the OldDbDataAdapter, DataSet, and DataGrid classes, a short program can marry a database adapter to a data grid to display a database table as follows:
using System.Windows.Forms; using System.Data; using System.Data.OleDb; using DataLayer; using UserInterface; public class ShowAdapter : Form { public ShowAdapter() { DataSet d = new DataSet(); string s = "SELECT * FROM Rocket"; OleDbDataAdapter a = DataServices.CreateAdapter(s); a.Fill(d, "Rocket"); a.Dispose(); DataGrid g = UI.NORMAL.CreateGrid(); g.SetDataBinding(d, "Rocket"); Controls.Add(g); Text = "All My Rockets"; Font = UI.NORMAL.Font; } static void Main() { Application.Run(new ShowAdapter()); } }
This program creates an OleDbDataAdapter object that reads all the data in the Rocket table in the database. (The Rocket table is actually a “query” in the oozinoz.mdb Microsoft Access database. You can download this database and all the files that go with this book from www.oozinoz.com. See Appendix C, “Oozinoz Source,” for help with obtaining the source.)
The adapter's Fill() method creates a DataTable object within the dataset and names the table "Rocket". The program then releases its database resources with a call to Dispose() and creates a DataGrid object as the only control in the form. The SetDataBinding() method causes the grid to display data from the Rocket table within the supplied dataset.
Running this program produces the display shown in Figure 3.7.
Figure 3.7. A few lines of C# code can produce this presentation of a database table's contents.
This example shows a flow of data from database through to presentation, but does not show the ADAPTER pattern at play. If ADAPTER were present, we would see an interface that defines the DataGrid class's needs (for a class adapter) or we would see subclasses of DataGrid (for an object adapter).
The lack of ADAPTER in this example does not imply that the design is inflexible. In fact, the logic in the SetDataBinding() method accepts many different types of arguments. A program can pass this method a DataSet instance, a DataTable instance, a DataView instance, a DataViewManager instance, or an instance of any class that implements the IListSource or IList interfaces. The method can also accept a one-dimensional array. Flexible indeed! But, rather than handling so many different sources of data, the DataGrid class might actually be more flexible if it defined its expectations in an interface. Figure 3.8 shows this approach, along with a class that adapts the interface to an instance of IList.
Figure 3.8. This design uses an adapter to adapt a source object's data to the needs of a DataGrid object.
Note that the design in Figure 3.8 is merely a proposal: The existing FCL classes are not laid out this way and there is no ITable interface in the FCL. The design suggests that the developers of the DataGrid class might supply an ITable interface to define the DataGrid class's needs. Then, instead of building support for IList objects into the DataGrid class's SetDataBinding() method, a ListTable class could adapt a list's data to appear as a table.
A solution appears on page 353.
As data flows from persistent storage through business layers and into presentation code, there are often opportunities to adapt a data source to meet the needs of a data consumer. The ADAPTER pattern is not too prevalent in .NET, and a greater presence would arguably slim down control classes and provide more flexibility in how adaptation occurs.