Chapter 4: Building Datacentric Applications
Summary: Explore four applications to see how Visual Studio 2005 and the .NET Framework 2.0 enhance productivity and make connecting to data sources from your application much easier than with previous development tools. (31 printed pages)
Chapter 1 introduced you to the basics of ADO.NET and how data access has changed from Visual Basic 6. These next four applications introduce you to the enhancements in Visual Studio 2005 and the .NET Framework 2.0 for building data-centric applications. ADO.NET is a big topic and could easily fill an entire book on its own. These applications are intended as starting points from which you can further research ADO.NET using the MSDN documentation and online resources such as http://msdn.microsoft.com.
One of the most common frustrations for developers is having to learn new versions of a product that make older components obsolete or significantly change the way to do common tasks. The inevitable learning curve can often negatively affect productivity in the short term.
Microsoft set a goal for ADO.NET 2.0: enhance and extend it without changing it or breaking what already works. The same functionality from 1.1 is still available in version 2.0, but there are now more productive ways of performing common database tasks. For example, the Data Sources window provides a central place to create and configure the related objects required to access a data source (for example, SQL Server). Smart tags added to controls provide fast access to common development tasks, such as changing the type of a control (for example, changing TextBox to Label). You can also use smart tags to reconfigure data sources and even create new queries to pull out filtered data based on user input (that is, create parameterized queries).
After exploring the next four applications, you will see how Microsoft Visual Studio 2005 and the .NET Framework 2.0 enhance productivity and make connecting to data sources from your application much easier than with previous development tools.
In this first application, you are introduced to the new Table Adapter and BindingSource classes. These two classes will help to simplify your ADO.NET development tasks by providing powerful features that you can use in your application right away while, at the same time, limiting the amount of code that you have to manually write to perform common tasks.
As mentioned in Chapter 1, a primary design goal for ADO.NET was first-class support for working with disconnected data. ADO.NET provides an intuitive way of dealing with disconnected data through a set of classes that are separated into various namespaces. You do not need to know every class of every namespace to effectively use ADO.NET. Instead, you can concentrate on the namespace containing the classes required to access your particular data source. Table 4-1 lists some of the most common namespaces and classes that make up ADO.NET.
Table 4-1. ADO.NET Namespaces
|System.Data||General classes used throughout ADO.NET||DataSet, DataTable, and DataView|
|System.Data.Common||New set of base classes that are used by the different data providers in ADO.NET (such as SqlClient, OleDb, and so on)||DataAdapter, DbCommand, and DbConnection|
|System.Data.SqlClient||Contains the data provider classes for connecting to Microsoft SQL Server||SqlConnection, SqlCommand, and SqlDataAdapter|
|System.Data.SqlTypes||Consists of classes used to represent the native data types used in SQL Server||SqlString, SqlDateTime, and SqlInt32|
|System.Data.OleDb||Consists of classes that are used to access OLE DB data sources||OleDbConnection, OleDbCommand, and OleDbDataAdapter|
|System.Data.ODBC||Classes used to connect to ODBC data sources||OdbcConnection, OdbcCommand, and OdbcDataAdapter|
|System.Data.OracleClient||Classes used to access Oracle data sources||OracleConnection, OracleCommand, and OracleDataAdapter|
|System.Xml||Consists of classes that provide standards-based support for accessing and working with XML as data||XmlAdapter, XmlReader, and XmlWriter|
A DataSet object is used to hold data from one or more data sources. It maintains the data as a set of tables with optional relationships defined between those tables. The DataSet class is meant to be generic, so it is not tied to any specific type of data source. Data can be loaded into a DataSet from many data sources, including Microsoft SQL Server, Oracle databases, Microsoft Access, Microsoft Exchange, Microsoft Active Directory, or any OLE DB or ODBC- compliant data source.
Although not tied to any specific data source, the DataSet class is designed to hold relational tabular data as you would find in a relational database such as Microsoft SQL Server.
Figure 4-1 is a visual representation of two related tables in a DataSet.
Figure 4-1. Visual representation of two related tables in a DataSet
Each table shown in Figure 4-1 is realized in the DataSet as a DataTable. The relationship between the two tables is realized in the DataSet as a DataRelation object. The DataRelation object provides the information that relates a child table to a parent table via a foreign key. Keep in mind that Figure 4-1 is merely a visual representation of the structure of a specific instance of a DataSet. A DataSet can hold any number of tables with any number of relationships defined between them.
Figure 4-1 is actually a screen capture of the new DataSet Designer in Visual Studio 2005. Figure 4-2 shows the DataSet Designer being used to edit the structure of a data source in the new Data Sources window. The DataSet Designer is discussed further in the "Walkthrough" section for this application that appears later in the chapter.
Figure 4-2. Using the DataSet Designer to edit a data source
Retrieving Data from a Database
Although there are several ways to use a DataSet, the most common use is to connect to a database and fill a DataSet with data from the database. In ADO.NET, each data provider (SqlClient, OleDb, and so on) provides a set of specialized classes for interacting with a data source. To retrieve data from a database, you connect to the database, execute a query or stored procedure, and then populate the DataSet with the results. To do all this requires a set of objects that work together, including connection and command objects.
Each ADO.NET data provider includes a specialized connection class for connecting to its supported data source types. For example, the SqlConnection class allows your applications to connect to SQL Server 7.0 or higher. You could also use an OleDbConnection object, but the SqlConnection class is optimized for working with SQL Server, making it a better choice. For data sources that do not have a specific ADO.NET data provider, you can use an OleDbConnection object as long as there is an OLE DB provider available for the data source. The OleDbConnection class is what you would use to connect to non-database data sources such as delimited text files.
Once you have established a connection to the data source, additional objects from the data provider have to pull data from the data source and use it to populate a DataSet. The way to populate a DataSet in Visual Studio .NET 2003 is to use a data adapter object to fill the DataSet with data. Each ADO.NET data provider includes its own unique data adapter class. The SqlClient provider, for example, includes the SqlDataAdapter class. The SqlDataAdapter class requires a SqlCommand object and a SqlConnection object in order to retrieve data. You can provide a SELECT statement or a stored procedure to the data adapter and have it generate its own SqlCommand object, or you can explicitly create your own SqlCommand object for selecting data and assign it to the SelectCommand property of the SqlDataAdapter. Calling the Fill method of the data adapter tells the data adapter to execute its SelectCommand and fill the DataSet with the data retrieved by the SelectCommand. The following code is an example of filling a DataSet with data by using a SqlDataAdapter.
Dim conn As New SqlConnection conn.ConnectionString = _ "server=(local);database=Northwind;Trusted_Connection=True" Dim adapter As SqlDataAdapter adapter = New SqlDataAdapter("SELECT * FROM Products", conn) Dim dsProducts As New DataSet adapter.Fill(dsProducts)
Prior to Visual Studio 2005, the data adapter was the only link between the DataSet and the actual data source. If you made changes to the data in the DataSet, you required a different data adapter for each table in the DataSet and had to call the Update method of each data adapter.
Although the data adapter approach for reading and updating data is completely supported in Visual Studio 2005, there are also some new ways to read and write data that reduce and simplify the code that you have to write. In this application, you will see how to establish a database connection and retrieve data from the Northwind database by writing only one line of code. This application highlights some new data components and features in Visual Studio 2005 and the .NET Framework 2.0, including table adapters and the BindingSource class.
With Visual Studio 2005, you can create data-driven applications that present your data in a data grid with just one line of code. Of course, more information is required than what is contained in that one line of code. But the tedious work of writing lines and lines of code for setting up the database connection, filling multiple data tables one at a time, and doing the data binding is all handled for you by Visual Studio 2005. Two new IDE features, the Data Sources window and the Data Source Configuration Wizard, assist you in setting up data access by using the new classes, such as BindingSource.
This application begins as a new Windows application named TableAdaptersAndBindingSource. Depending on your IDE settings, Visual Studio 2005 might not display the Data Sources window automatically. If the Data Sources window is not displayed, you can select the Data | Show Data Sources menu command. This will open the Data Sources window as shown in Figure 4-3.
Figure 4-3. The Data Sources window
Next, you can click the Add New Data Source link or the Add New Data Source button on the window's toolbar. This will display the opening page of the Data Source Configuration Wizard. Read the welcome message and then click Next to start the configuration of your data source as shown in Figure 4-4.
Figure 4-4. The Data Source Configuration Wizard
The Data Source Configuration Wizard supports three types of data sources. The first option, Database, allows you to create a programmatic data source for a database server or local database file. If your data will come from a Web service, you can select the second option to add a new Web Reference to your project. The third option is Object. This option allows you to bind your user interface to one of your own classes. Object data sources are covered later in the "Application: Object Data Source" section. This first application uses a database as a data source.
The next step in the Data Source Configuration Wizard is to either select an existing connection or create a new connection for your data source. The first time you run the wizard there are no pre-existing connections available. But on subsequent uses of the wizard you can choose to reuse previously created connections. When you click the New Connection button in the wizard, the Connection Properties dialog is displayed, as shown in Figure 4-5. You can use this dialog to configure all the settings for connecting to your database server.
Figure 4-5. The Connection Properties dialog
The wizard will give you the option to save the resulting connection string to the application configuration file (app.config). If you will use the same connection again in your project, having the connection string saved to app.config automatically is very convenient. Also notice that you can now connect directly to SQL Server databases by simply providing the path to the .MDF file. This is supported with both SQL Server 2005, and SQL Express.
The next configuration step in the wizard, shown in Figure 4-6, asks you to choose the database objects for this data source. You can choose to select any number of tables, views, stored procedures, and functions.
Figure 4-6. Selecting database objects in the wizard
When you select a database object such as a table, you can expand its node in the tree view (seen in Figure 4-6) and choose to select only specific columns from that table. After you select your database objects, the wizard will build your SELECT statements (or stored procedure and function calls) for you automatically.
After the Data Source Configuration Wizard is finished, a new data source is added to the Data Sources window. (For an example, see Figure 4-3.) The Data Sources window displays the data source as a tree view with a root DataSet node containing a node for each database object (table, view, and so on) selected in the Data Source Configuration Wizard. If you expand one of those tables, as shown in Figure 4-7, you see that only the fields selected in the Data Source Configuration Wizard are included in the DataSet.
Figure 4-7. DataSet expanded in the Data Sources window
If you want to display the data from one of the tables in a grid on a form, you simply drag the appropriate node from the Data Sources window to your form. Visual Studio 2005 will automatically create a data-bound DataGridView control and an accompanying navigation bar at the top of the form. The resulting form is shown in Figure 4-8.
Figure 4-8. A form with a data-bound DataGridView control at design time
If you have used Visual Studio .NET 2003 to create data-driven applications, you have probably dragged a data adapter onto a form at some point and gone through the Data Adapter Configuration Wizard. The Data Adapter Configuration Wizard places connection and data adapter objects in the component tray of your form. Visual Studio 2005 adds four objects to the component tray: a DataSet, a BindingSource, a table adapter, and a DataNavigator.
Note The component tray is a special area in the IDE that displays controls hosted on a Windows or Web form that do not have a user interface. In Visual Basic 6, controls without a user interface (for example, Timer) still had to be placed on the form design surface, making design-time control placement awkward.
The DataSet added to the form in this application is simply an instance of the DataSet class that was created automatically when the data source was created.
The BindingSource is a new class in the .NET Framework 2.0. It acts as a data source for other controls to bind to, and keeps track of the current record position. This eliminates the need to use the CurrencyManager from version 1.0 /1.1 of the .NET Framework. The data it exposes is the data that the BindingSource consumes from its own data source. The BindingSource can also pass commands from the data consumer through to the data source.
Table adapters are new to Visual Studio 2005. They are designer-generated components that connect your DataSet objects with their underlying data sources. When you create data components in an application, such as through the Data Source Configuration Wizard, one or more custom table adapters are created automatically. Table adapters are similar to data adapters, but they are strongly typed, which means each table adapter is a unique class that is automatically generated by Visual Studio 2005 to work with only the fields you have selected for a specific database object (for example, a Products table). Table adapters also make it very simple to pass parameters to parameterized queries and stored procedures.
When you create a database application using ADO.NET, you generally make extensive use of DataSet objects. A DataSet holds your data, but it relies on other objects to move data to and from the underlying data source. In the .NET Framework 1.1, data adapter objects were the lifeline to the data source and you had to create a data adapter for every DataTable in your DataSet. For example, if you were to create a DataSet to hold the Orders and Order Details tables from the Northwind database, you would need two data adapters.
The new table adapters in the .NET Framework 2.0 are unlike data adapters in that they can contain multiple queries, letting you group all operations associated with a particular table. In this application, there is only one query in the table adapter (for the Customers table). If you right-click the Customers table in the Data Sources window and select Edit Data Source With Designer, the structure of the data returned for Customers is opened in the DataSet Designer as shown in Figure 4-9.
Figure 4-9. The CustomersTableAdapter query
As you can see in Figure 4-9, there is an item labeled Fill,GetData() at the bottom of the Customers table. This item contains the connection and query information for the Customers table in the table adapter. If you look at the CommandText property (in the Properties window), you can see the start of the SELECT statement used to populate the Customers table.
If you require filtered views of the data for a table, you can create additional queries to fill the table in the DataSet. For this application, you would right-click the CustomersTableAdapter heading of the Customers table (shown in Figure 4-9) and select the Add Query menu command.
Note If you create multiple queries in the table adapter, you must ensure that the returned data maps correctly to the table schema; otherwise, the query will fail.
The new DataNavigator component provides services for navigating through data that is bound to user interface controls. The services it provides are somewhat analogous to the Data control from Visual Basic 6, which provided navigation services via a set of navigation buttons. The DataNavigator component in ADO.NET is designed to allow navigation among the records in a DataSet. It also provides services that allow for adding, editing, and deleting the data.
The functionality of the DataNavigator control is exposed through one or more ToolStrip controls, shown previously in Figure 4-8. The ToolStrip buttons perform tasks such as adding and deleting records.
If you have used the previous versions of ADO.NET, you know that navigation through records in a DataSet was not as simple as it could have been. With an ADO Recordset, you had MoveFirst, MovePrevious, MoveNext, and MoveLast methods that allowed navigation through the Recordset data. These navigation features were made possible through the use of a cursor that was used to maintain your current position in the Recordset and to navigate through the records. The ADO.NET DataSet, which replaced the Recordset object as the central record- based data object, has no cursor, nor does it have any knowledge of the data store from which its contents came. Without a cursor or equivalent object to maintain position, navigation was not so easy.
Binding controls on a form to your ADO.NET DataSet and providing navigation services that keep all controls in sync is accomplished through the use of two objects known as the CurrencyManager and the BindingContext. For each data-bound form that you have in your application, you will have at least one BindingContext object. This object will be responsible for any CurrencyManager objects that exist. You will have one CurrencyManager object for each data source on your form. The CurrencyManager object is responsible for keeping track of your position in the DataSet and for the overall supervision of the bindings to the data source. You call the necessary methods on the CurrencyManager object to navigate through the records, and the BindingContext ensures that all bound controls are kept in sync. In the .NET Framework 2.0, this functionality is provided by the BindingSource class, and the DataNavigator class communicates with the BindingSource to allow the user to navigate through records.
The components for this application are created and wired together using the visual designer. The navigation bar will function correctly without any code. The only code needed is one line that uses the table adapter to fill a DataSet by pulling data from the data source. This is inserted automatically in the Load event handler for the form as follows:
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Me.CustomersTableAdapter.Fill(Me.NorthwindDataSet.Customers) End Sub
This event handler and the code to fill the dataset and populate the grid are added automatically by Visual Studio 2005. All you have to do is press F5 and see a grid of data loaded from a database. What is accomplished in this application is no small feat, despite the deceptive simplicity of just pointing and clicking. If you look at the InitializeComponent method, you will see approximately 300 lines of code that you don't have to write—a substantial productivity enhancement!
Although there is a lot happening, this application is somewhat simple. It shows the basics of connecting to a database and populating a grid control. The remaining data applications will highlight other aspects of ADO.NET in Visual Studio 2005.
The DataSet Designer
It is worth taking some time at this point to look more closely at the DataSet Designer. This utility has been significantly overhauled and is one of the more prominent enhancements to data access in Visual Studio 2005.
In Visual Studio .NET 2003, when you wanted to edit the structure of a DataSet, you did so by editing an XML Schema (XSD). Although there was a visual designer, the terminology and user interface were not consistent with a DataSet and its constituent objects (for example, a DataTable).
With the new Data Sources window, you can right-click it and choose Edit Data Source with Designer. This will open the Dataset Designer and allow you to make changes to the structure of the DataSet. With this view of the DataSet, you can graphically manipulate the tables and queries in a manner more directly tied to the DataSet rather than having to deal with an XML Schema.
To help you understand some of the capabilities of the Dataset Designer, consider a potential issue that can arise in just about any application that will deal with data from a database. There can be times when you have forgotten to add a table that is required for an application to display data. With Visual Studio 2005, you can simply add the missing table in the DataSet Designer window by right-clicking a blank area of the designer surface and choosing Add | Table Adapter. The designer adds a new table to the DataSet Designer window and opens the Data Component Configuration Wizard.
The Data Component Configuration Wizard is similar to part of the Data Source Configuration Wizard. The Data Component Configuration Wizard prompts you to set up a connection to the data source for the new table and to provide or select queries or stored procedures for data read and write operations. From the wizard, you can launch a QueryBuilder to create a query graphical interface. The tables, views, and functions in the database are available in the QueryBuilder as shown in Figure 4-10.
Figure 4-10. The graphical QueryBuilder
After setting up queries in the wizard, you can choose to have several specialized methods added to your DataSet. The Fill method, which you can rename as required, is used to populate the table with data. The GetData method, which you can also rename as required, returns a reference to a populated instance of the DataTable. You can also choose to have methods created for updating the data source with data changes. The wizard page is shown in Figure 4-11.
Figure 4-11. The Data Component Configuration Wizard
When you add a new table to the DataSet, the wizard reads the database schema and adds any appropriate relationships to tables that are already in the DataSet. These relationships are represented as lines between tables in the DataSet Designer, as shown in Figure 4-12.
Figure 4-12. Related tables in the DataSet Designer
One aspect of the designer that can be disconcerting is that the relationship line merely depicts the fact that the two tables are related. But obviously the relationship must be between fields in the table. A visual inspection of the fields in each table can usually make the relationship evident. But if the field list is extremely long, it can be harder to quickly infer the relationship. Fortunately, the designer provides a feature that lets you more clearly indicate the relationship graphically by displaying and editing a label. If you right-click a relationship line and select Show Relation Labels, the relationship name is displayed in the visual designer.
The default name FK_Orders_Customers does not indicate how the Orders and Customers tables are related—it simply tells you that there is a relationship between the two tables. To edit the label so that it indicates a relationship, right-click the relationship line and choose Edit Relationship. This opens the Relation dialog shown in Figure 4-13.
Figure 4-13. The Relation dialog for editing relationships
The Relation dialog lets you manage the relationship, including the tables and columns that make up the relationship. Changing the name of the relationship will change the label displayed in the DataSet Designer. Changing the name of the relationship to Customers_CustomerID_Orders would make the relationship more self-explanatory, as it expresses that the tables are related on the CustomerID field.
The Relation dialog also allows you to change the type of relationship (foreign key constraint, relation, or both) and what happens to related records when a parent record is updated or deleted (that is, do we use cascading, does it become SetNull, SetDefault, or does nothing happen). You can also configure the relationship so that changes are automatically accepted or rejected in child records when changes to the parent record are accepted or rejected.
As you can see from this application, Visual Studio 2005 makes data access and display using a grid very easy to configure. The new table adapter model bundles together database connection, command, and data adapter objects, providing a simplified programming model for data-driven applications. With one simple line of code and a few mouse clicks, you have a working database application that will allow database updates, additions, and deletions. And the new DataSet Designer allows you to configure the settings and edit the structure (and subsequent behavior) of your data sources with an easy-to-use visual designer.
The new BindingSource class acts as an intermediary between data access objects and data- consuming objects. The new DataNavigator component provides navigation services for moving between, adding, and deleting records. A DataNavigator component manages ToolStrip controls that provide the user interface for moving through records of data. This application uses all these new features to create a useful and practical data-driven user interface with only one line of code entered by hand.
Smart tags are new features in Visual Studio 2005 that provide easy access to common tasks. This application uses new smart tags for data-bound controls to create and configure the user interface.
Smart tags are generally an entry point into wizards or other tools that let you configure certain aspects of your data sources or data components. Smart tags provide fast access to the most common tasks that developers perform on different types of controls and components. Smart tags are accessible via a small icon that appears when you select or hover your cursor over a control.
This application consists of a data-bound form that shows data from the Suppliers table in the Northwind database one record at a time (Details view). The purpose of this walkthrough is to explain smart tags. The side effect of completing this walkthrough is that you end up with a working application, but the application itself is not the point of this section. The next application discusses detail and master-details data forms in more detail.
The Data Sources window will be used to add a new data source that points to the Suppliers and Products tables in the Northwind database.
If you select a table in the Data Sources window, a drop-down arrow will appear with a list of view options from which you can select. The default is DataGridView, which is used in the Table Adapters and BindingSource application presented earlier. One of the other options in the drop-down list is Details, as shown in Figure 4-14. If you configure a table in the Data Sources window to use the Details view, dragging the table onto a form will prompt Visual Studio 2005 to create individual controls for each field and a DataNavigator component for navigating through the records, as shown in Figure 4-15.
Figure 4-14. Changing the type of user interface created from a Data Source table
Once this change has been made, you can simply drag and drop the suppliers table on to the form designer to automatically generate the user interface shown in figure 4-15.
Figure 4-15. A Details view user interface
In some data-driven applications, users need to see primary key data. In this application, the user sees the SupplierID value, which is a primary key field that is auto-generated by the database. Because users should not change this value, the user interface should not enable them to enter a new value. In most cases, you would simply disable the TextBox control or change it to a Label control. However, the data sources window makes it easy to determine what control you want to use when the field is placed on the form, or even change the control type after-the-fact. You can simply delete the SupplierID label from the form, and then, click the drop-down next to SupplierID in the data-sources window. Here, you can change the control type from a TextBox to a Label. Now, you can simply drag this individual field on to the form, and a Label control will be generated.
Figure 4-16. Changing a TextBox to a Label
A more powerful smart tag feature in Visual Studio 2005 is the ability to create master-details forms. With this feature, you can transform, with relative ease, a simple Details view form into a more complex master-details data form.
This application uses two related Details views on the same form: a parent section for suppliers and a child section for products supplied by each supplier. The resulting form is shown in Figure 4-17.
Figure 4-17. Master-details view created with smart tags
Starting from the original form shown in Figure 4-15, you can use data sources window to create and configure the details section to create a form like that in Figure 4-17. If you expand the Suppliers table in the data sources window, you will see that it contains a related Products table. You can click on the drop-down for this table, and set it for Details view, as shown in Figure 4-18. Then, you can drag and drop this table on to the form which will create the user interface controls, ProductsBindingSource, and ProductsTableAdapter. You can then rearrange the user interface controls, and add a BindingSourceNavigator. By setting the DataSource property of the BindingSourceNavigator to the ProductsBindingSource, you can allow the user to navigate through the products for a particular supplier, as shown in Figure 4-19.
Figure 4-18. Creating a master-details form by using smart tags
Figure 4-19. Navigating through suppliers and related products
This application uses a Details view to have the products displayed one record at a time (rather than in a grid. This means that some method is needed to control the navigation through the products for each supplier. This navigation is handled by a second DataNavigator component.
After adding the details section to the form, you should notice that Visual Studio has added one line of code to the Load event handler for the form to populate the Products table in the DataSet:
When you run this application, you can navigate through the suppliers and see the first product record change for each supplier. You can then navigate through each product record for each supplier.
Although there are not many smart tags for data controls, you can see that they make development of data-centric applications in Visual Studio 2005 much easier than in Visual Basic 6 or even Visual Studio .NET 2003. This application introduced smart tags and some common tasks you can accomplish with them. The walkthrough of the next application shows how to create parameterized queries to easily filter your result sets.
Most data-driven applications require more than simply binding one table to one grid control. Your data sources are more likely to contain multiple related tables. As a result, you will often find yourself creating applications that need to make use of a master-details view of the data. And a simple one-at-a-time navigation scheme is rarely enough for users to work with real- world quantities of data. Users need to be able to search and filter records. This application shows how you can use new data features to build better data applications faster.
Visual Studio 2005 and the .NET Framework 2.0 contain a number of new features that make it easier to create master-details views of your data. After creating a set of data-bound controls, you can have Visual Studio 2005 automatically create either a grid or Details view of child records in a related table with only one line of code required to fill the DataSet with data from the related table.
If you use the default settings when creating your data source and data-bound controls, your applications will show users all the data in your application. An important feature is the ability for users to enter search criteria and see only data that matches the search criteria. With the new Search Criteria Builder in Visual Studio 2005, you can add a parameterized query to your application and a user interface for searching and filtering data without writing any code!
In this application, order data from the Northwind database is displayed for each customer by using a master-details view. This setup requires data from two tables from the database to be displayed on a form. The two tables are related in the database already, so Visual Studio 2005 will detect the relationship. However, you have to provide some additional information to ensure that the appropriate orders are displayed when you navigate from one customer to the next.
Application User Interface
The application is a standard Windows application that contains a master-details form with customer information (master data) at the top of the form bound to individual controls. The customer orders (detail data) are displayed in a grid. Figure 4-20 shows the application user interface.
Figure 4-20. The application user interface
Using a grid control for the orders allows the user to see more orders for a customer simultaneously. A Details view section can also display one record at a time, but the approach used for this application is standard in many line-of-business applications.
The first step in creating a master-details form is to configure a data source. One nice feature of Visual Studio 2005 is its ability to remember previously created connection strings, which you can select from rather than redoing the same configuration setup. This feature is one of many small but useful productivity enhancements that you will find in Visual Studio 2005.
The data for this application comes from the Orders and Customers tables from the Northwind database. After the data source has been created in the Data Sources window (as described in the previous application), controls can be added to a form. Some controls will be bound to the master data, and some controls will be bound to the detail data. And then the crucial link has to be established between the master data and the detail data so that only the orders for the currently selected customer are displayed.
In the Table Adapters and BindingSource application, a DataTable was dragged onto a form from the Data Sources window and a grid control was created automatically. A grid view is the default view in Visual Studio 2005, but it is not the only option when using the Data Sources window to automatically generate controls. If you select a table in the Data Sources window, a drop-down arrow will appear that provides a list from which you select different view options. The default view is DataGridView, which is used in the Table Adapters and BindingSource application and which will be used for the details section of this application. One option in the drop-down list is Details. If you configure a table in the Data Sources window to use Details view, dragging the table onto a form will cause Visual Studio 2005 to create individual controls for each field and a DataNavigator component for navigating through the records.
Once the master section is on the form, you can provide the linking information between the master data (customer information) and the detail data (customer orders). Visual Studio 2005 makes this extremely easy, especially compared to the process of linking data together manually using Visual Studio .NET 2003. When the Customers table is dragged from the Data Sources Window and dropped on the form, it creates the user interface elements for the master information. Under the Customers table, in the Data Sources Window, you will notice the related Orders table, as shown in Figure 21. Dragging this Orders table on to the form creates a DataGridView which is bound to the relationship between Customers and Orders. With this binding, only the orders for the selected customer will be rendered in the DataGridView.
Figure 4-21. Adding relationships to data-bound items on the form by dragging a related table
From the Data Sources window, you first expand the parent table and then select the child table. Next, you decide whether to display the child rows as a grid or in a details format. This can be selected by clicking the drop-down arrow to the right of the table. In this application, Customers is the parent table and Orders is the related child table. The child data is displayed using a grid. You can then just drag the Orders table on to the form. The Orders grid is added to the form automatically. Table adapter and BindingSource components are also added to the form. The table adapter, named OrdersTableAdapter in this application, provides the logic for filling a DataSet with order data. The BindingSource serves as the data source for the DataGridView control that displays the order data.
To display any data in this application, the tables in the DataSet must be filled with data. There are two table adapters in this application: one for customer data, and one for order data. When the Fill method is called on a table adapter, the table adapter fills its corresponding table in the DataSet with data from the database. To fill both tables in this application, you have to call the Fill method for each table adapter. The following code in the Form1_Load event handler calls both Fill methods:
If you were to run the application at this point, you could navigate through the various customers with the navigation controls. The order information in the grid would change according to the currently selected customer.
For a trivial demonstration, simply browsing through records one at a time is fine. But in the real world your users will need to be able to locate a specific customer quickly. Visual Studio 2005 helps solve this problem by allowing you to create a parameterized query for your forms.
In this application, users will be able to type a customer name and navigate directly to the customer's record. To enable this type of lookup, you first select the CustomersBindingSource's smart tag, and then select the Add Query operation. (You can also access this feature through the Data menu.) Following these steps will open the Search Criteria Builder dialog shown in Figure 4-22.
Figure 4-22. The Search Criteria Builder dialog used to build a parameter query
The Search Criteria Builder assists you in creating a parameterized query that will return a filtered result set based on the user's input. There are essentially two ways to build this query: you can write the SQL yourself, or you can use the visual Query Builder. Visual Studio 2005 attempts to provide most of the query for you by examining the fields you have already selected and bound to the controls on your form. In most cases, you simply have to complete the WHERE clause. Just scroll to the end of the SELECT statement already provided in the query text box and add a filter such as the following:
WHERE CompanyName LIKE @Company
This addition tells the query to select all the chosen fields where the value in the CompanyName field matches the pattern that the user will enter (for example, B%). The @Company parameter is an input parameter and will be replaced with the value that the user enters in the user interface. The parameter name can be whatever you want. It is called @Company in this application to reflect its purpose in the query—to filter the results by company name.
The name of the query, which you also configure in the Search Criteria Builder, also becomes the name of the method used to fill the appropriate DataTable in your DataSet with the results of your query. The Search Criteria Builder encourages the use of the FillBy prefix when naming queries. You do not have to use this in your query name, but it is a good idea to leave it as a prefix. In this application, the query is named FillByName.
As mentioned, you can also build your query graphically by using the QueryBuilder as shown in Figure 4-23. One of the greatest benefits of creating your query this way is the ability to validate the query prior to having it execute in your code.
Figure 4-23. The QueryBuilder dialog used to graphically build a query
Important If you decide to generate your query by using the QueryBuilder, ensure that you have selected the same fields that are present on your form. If you fail to select the appropriate fields, some of your controls on the form might not contain any data.
After you configure a new parameterized query, a new ToolStrip bar is placed on your form for the user to enter criteria for your new query. In this application, the user will enter the company name as a parameter to the query in the FillByNameToolStrip.
When you first start the application, the default Fill method that was created when the data source was created is executed in the Form1_Load event handler. If you then type a value (for example, B's Beverages) into the FillByCompanyNameToolStrip bar and press Enter (or click FillByCompanyName), the master records are filtered. As you then navigate through the filtered records (if there is more than one matching record), the details grid is updated to show the orders for the current customer. Figure 4-24 shows the application with a filtered set of master records.
Figure 4-24. Filtered set of Customer records
The previous application introduced the DataNavigator, but it did not drill down into the amount of coding effort that it actually saves you when writing data-driven applications. You will no doubt have noticed that the DataNavigator not only contains controls for moving through records but also includes controls for adding, deleting, and saving records.
Obviously, many database applications have limited value without the ability to add, edit, and delete data and then save the changes. In this application, code for the Save button has already been added by Visual Studio to its click event handler to propagate changes to the database:
Private Sub CustomersBindingNavigatorSaveItem_Click( _ ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles CustomersBindingNavigatorSaveItem.Click Me.Validate() Me.CustomersBindingSource.EndEdit() Me.CustomersTableAdapter.Update(Me.NorthwindDataSet.Customers) End Sub
Notice the various objects and methods that are involved in doing the updating. The changed data is in the Customers table in the NorthwindDataSet. The changed data is passed to the Update method of the CustomersTableAdapter, which sends the changes to the database. The Update method passes insert, update, and delete changes to the database.
You might be wondering how the Update method can find the deleted rows in the Orders table since they were deleted. What in fact happens is that deleted rows are simply marked as deleted and left in the DataTable. This behavior allows you to later access the rows that have been marked for deletion and choose whether to submit the deletions to the database. The RowState of a DataRow object in the DataTable indicates whether a row is deleted, added, modified, unmodified, and so on.
When you call the CustomersTableAdapter.Update method, you are actually causing SQL statements to be sent to the database for execution. The RowState of a row determines whether the SQL statement to use is an INSERT, UPDATE, or DELETE statement. When you create a Data Source and use the Data Source Configuration Wizard to automatically generate INSERT, UPDATE, and DELETE queries for you, Visual Studio 2005 actually creates the queries and all the code for using them.
The location where this code can be found has changed in Visual Studio 2005. You have to navigate to the NorthwindDataSet.Designer.vb code module to see this code. Where previous versions of Visual Studio .NET placed this code directly in the Windows Forms Designer Generated code section, you now have to choose View All Files on the Solution Explorer window and then expand your NorthwindDataset.xsd view to see the code module.
If you view this application with Visual Studio 2005, you will wonder where this code came from because Visual Studio 2005 does a good job of hiding it. Visual Studio 2005 uses something referred to as "partial classes" to separate the designer and developer written code. Code generated by the developer is stored in a regular .vb file, as with Visual Studio .NET 2003. But code generated by the designer is stored in a separate .vb file. The reason for this design is quite simple. In Visual Studio .NET 2003, designer-generated code was wrapped in the same .vb file as the developer's code, but it was wrapped in a #Region. Some programmers had to (or chose to) edit some of the code created by the Visual Studio .NET 2003 tools and wizards, despite recommendations not to do so. Because the designer generated the code, the designer was also entitled to rewrite the generated code, thus overwriting any changes made by a developer.
To resolve this issue, Visual Studio 2005 places the code that you write in your .vb file as before, but it is completely separate from the designer-generated code that you will find in a .Designer.vb file. Each item in your project (for example, a form) can have both a .Designer.vb file and a regular .vb file. By default, you do not see the designer files in the Solution Explorer. You can force Visual Studio 2005 to show them by turning on the Show All Files feature in the Solution Explorer.
The few lines of code written here plus the plethora of designer-generated code creates a fully functional database application—including the ability to update the data and save it to the database.
This application demonstrates the ease with which you can build a data application that displays values from multiple related tables. The ability to easily create and configure a master- details style user interface in Visual Studio 2005 allows you to display related records simultaneously by writing only a trivial amount of code.
This application also introduced you to the new smart tags for data that allow you to quickly accomplish common tasks such as creating a parameterized query. Smart tags for data are discussed more in the next application, where you will see how to use them to your benefit to reduce the amount of work you have to do and the amount of code that you have to write.
This application shows you how to use an object as a data source in Visual Studio 2005.
One of the powerful new ADO.NET features in Visual Studio 2005 is a much more natural way to use an object as a data source. This object can be a pre-existing object or one that you create as a part of the project or as a separate class library. Although you could create your own objects in Visual Studio .NET 2003 and use them as data sources for things like data binding, it was cumbersome and code-intensive at best.
In the .NET Framework 2.0, any object that exposes public properties can serve as a data source. In this application, a simple class that represents a computer is used. The Computer class will contain properties for Model #, Serial #, CPU, amount of R AM, and hard-drive space. A data source based on this object is used to populate TextBox controls to display the various properties in the user interface.
To show an alternative way to do data binding, this application creates an array of Computer objects and uses the BindingContext to navigate through the array of objects in much the same way as you would navigate through records in a DataSet.
The user interface for this application is a simple form with a few labels, TextBox controls for the properties of the Computer class, and navigation buttons for viewing Computer objects one at a time. The form is shown in Figure 4-25.
Figure 4-25. The Object Data Source form
The Computer class is defined in a separate class library that the main Windows application references. The Computer class is a simple class that has five private member variables (m_Model, m_Serial, m_CPU, m_RAM, and m_HDSpace) exposed via public properties (Model, Serial, CPU, RAM, HDSpace). The following code defines the Computer class.
Public Class Computer Private m_Model As String Private m_Serial As Integer Private m_CPU As String Private m_RAM As String Private m_HDSpace As String Public Property Model() As String Get Return m_Model End Get Set(ByVal value As String) m_Model = value End Set End Property Public Property Serial() As Integer Get Return m_Serial End Get Set(ByVal value As Integer) m_Serial = value End Set End Property Public Property CPU() As String Get Return m_CPU End Get Set(ByVal value As String) m_CPU = value End Set End Property Public Property RAM() As String Get Return m_RAM End Get Set(ByVal value As String) m_RAM = value End Set End Property Public Property HDSpace() As String Get Return m_HDSpace End Get Set(ByVal value As String) m_HDSpace = value End Set End Property End Class
To create a data source based on an object, you start by adding a new data source in the Data Sources window, much like you would do if you were creating a data source for a database. But in the Data Source Configuration Wizard you select Object instead of Database when choosing the type of data source. (See Figure 4-26.) You then select the class you want to use as a data source by using the wizard. If your application does not already contain a reference to the assembly that defines the class you want to use, you can add a new reference right from the Data Source Configuration Wizard. (See Figure 4-27.)
Figure 4-26. Selecting an Object type data source in the Data Source Configuration Wizard
Figure 4-27. Selecting the object (class) to bind to in the Data Source Configuration Wizard
Binding Objects the Hard Way
The following code shows the hard way to tie the user interface to the array of Computer objects:
Private Computers(5) As Computer.Computer Private myBindingManager As BindingManagerBase Private Sub Form1_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load CreateComputers() txtModel.DataBindings.Add("Text", Computers, "Model") txtSerial.DataBindings.Add("Text", Computers, "Serial") txtCPU.DataBindings.Add("Text", Computers, "CPU") txtHDSpace.DataBindings.Add("Text", Computers, "HDSpace") txtRAM.DataBindings.Add("Text", Computers, "RAM") myBindingManager = BindingContext.Item(Computers) End Sub Private Sub cmdPrevious_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles cmdPrevious.Click myBindingManager.Position -= 1 End Sub Private Sub cmdNext_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles cmdNext.Click myBindingManager.Position += 1 End Sub Private Sub CreateComputers() Computers(0) = New Computer.Computer Computers(0).CPU = "Pentium 2.4GHz" Computers(0).Model = "GK2400" Computers(0).Serial = 123456 Computers(0).HDSpace = "200GB" Computers(0).RAM = "1GB" Computers(1) = New Computer.Computer Computers(1).CPU = "Pentium 2.8GHz" Computers(1).Model = "GK2800" Computers(1).Serial = 65123 Computers(1).HDSpace = "120GB" Computers(1).RAM = "512MB" Chapter 4: Building Datacentric Applications 127 Computers(2) = New Computer.Computer Computers(2).CPU = "Pentium 2.0GHz" Computers(2).Model = "GK2000" Computers(2).Serial = 12216 Computers(2).HDSpace = "80GB" Computers(2).RAM = "256MB" Computers(3) = New Computer.Computer Computers(3).CPU = "Pentium 3.0GHz" Computers(3).Model = "GK3000" Computers(3).Serial = 123124 Computers(3).HDSpace = "200GB" Computers(3).RAM = "2GB" Computers(4) = New Computer.Computer Computers(4).CPU = "Pentium 2.5GHz" Computers(4).Model = "GK2500" Computers(4).Serial = 154156 Computers(4).HDSpace = "120GB" Computers(4).RAM = "1GB" End Sub
The form includes a global array to hold five computer objects and a BindingManagerBase, which keeps track of the current position in the array of objects.
The Load event handler for the form calls the CreateComputers procedure to populate the array with values for five computers. This provides the values for the user to navigate through using the Next and Previous buttons. Then the TextBox controls are bound to the Computers array by using the DataBindings collection of each TextBox control.
The cmdNext_Click and cmdPrevious_Click click event handlers both use the BindingManagerBase to change the current position of the binding context, thus causing a different record to be displayed in the form.
An Easier Way to Bind Objects
Now that you have seen how to bind objects the hard way, it is time to look at doing this the easy way. Doing this will add the now familiar navigator bar at the top of our form. The only coding task you have to handle is a single line of code to provide the data. In this case, you have to set the DataSource property of a BindingSource rather than filling a DataSet.
Earlier in this walkthrough, you saw how to create a data source based on an object. The resulting view in the Data Sources window is set to DataGridView as shown in Figure 4-28.
Figure 4-28. The Data Sources window showing an Object data source Gridview
As with other types of data sources, you can simply click the drop-down and switch the default rendering from DataGridView to Details, and drag an object from the Data Sources window onto your form. Figure 4-29 shows the controls created by dragging the Computer object data source onto the form for this application.
Figure 4-29. Bound form for displaying object data
You can see that dragging the grid onto the form prompts Visual Studio 2005 to also create a navigator bar at the top of the form. At this point, the component tray contains ComputerBindingSource and ComputerDataNavigator objects as shown in Figure 4-30. You will also notice that there are no table adapters or DataSet objects in the component tray. That is because they have no purpose when dealing with an object as a data source.
Figure 4-30. Component tray with Object data source components
Binding to an object as shown in this application allows you to take advantage of the convenience of data binding while using business objects to represent your data instead of data-base-centric classes such as DataSet and DataTable. Using object data sources does put some extra responsibility on you as the developer because you have to provide the logic for saving changes made to the in-memory collection of objects. But the ability to work with your own business entity classes instead of DataTables can be beneficial in many situations, such as in n- tier systems that require the user interface to be completely ignorant of the type and structure of the underlying data stores for a given system. In such a situation, the presentation layer can retrieve a set of objects from a middle tier and bind those objects directly to the user interface. Certainly object data sources are not appropriate for all applications, but in many situations they are an extremely powerful and useful option. And Visual Studio 2005 makes data binding with objects amazingly easy.