You are on page 1of 35

Chapter Fourteen

14
Database Access

After this chapter you should

understand the basics of databases, entities and relationships

be able to create a database in Microsoft Access

understand what an entity relationship diagram is

be able write basic queries in SQL

be able to access a database from Visual Studio with C#

understand the basic components of ADO.NET and how to use


them to display data in a grid or databound text boxes and
update data

understand the difference between a data source and a data set


as well as the difference between typed and untyped data sets

Key concepts
Database
Entity Relationship Diagram
SQL
ADO.NET
Data sources and Data sets
Chapter 14 2 Databases

Database basics
Database system

A database system is essentially nothing more than a computerised record-keeping system.


Such a system involves four major components: a database, hardware, software and users.

A database is an organised collection of entities (things) and relationships that exist between
the entities. The database may be used by the application programs of a given enterprise. An
enterprise might be a single individual with a very small private database or a complete
corporation with a very large shared database (or anything in between).

The hardware portion of a database system consist of the secondary storage volumes together
with the associated I/O devices as well as the processor(s) and associated main memory that
are used to support the execution of the database system software.

Between the physical database and the users of the system is a layer of software, the database
management system (DBMS). All requests from users for access to the database are handled
by the DBMS. The DBMS may, in turn, consist of two elements: the application programs and
the database engine (DBE). The DBE provides access to the actual data while the application
programs are user-friendly interfaces between the user and the DBE. The application
programs may provide facilities to add, retrieve, update or delete data through queries, forms,
etc. We can use Microsoft® Access or a C# program to develop the application software.
Microsoft® Access has its own built-in DBE, the so-called JET engine, but if you write a C#
program you will have to use the ADO.NET components to access the data. Through ADO.NET
you can also access data in other database systems such as SQL Server, Oracle, DB2,
Interbase, etc.

There are mostly three classes of users involved with a database system: Application
programmers are responsible for writing application programs that use the database. This is
the main concern of this chapter: Teaching you how to write applications in C# that connects
with the physical database. End users interact with the database system from online
workstations or terminals through either the DBMS (MS Access in our case) or the application
programs mentioned above. The third class of user is the database administrator (DBA). This
person is responsible to create the actual database and to implement the technical controls
needed to enforce management's data policies. These policies determine what data should be
stored in the database and dictates who can perform what operations on what data in what
circumstances. The DBA is also responsible to ensure that the system operates with adequate
performance.

Entities and Relationships

Entities are any of the things of interest to an enterprise and about which the enterprise
collects data, such as PRODUCT, SUPPLIER, CUSTOMER, SALE, etc. (Entities are capitalised when
written in normal text.)

There are several kinds of database systems: hierarchical, network, relational and object-
oriented. We will focus on relational databases. A relational database stores the data for each
entity in a table with rows and columns. The columns in the table are called fields. The rows
are referred to as records. Each table in a relational database must have a primary key, i.e. a
field or combination of fields that are guaranteed to be unique from one record to the other.
For example, a customer number (key fields are underlined when written in normal text) can
be the key field for a CUSTOMER.
Chapter 14 3 Databases

Fields

Customer number Name Address Telephone


112W3 MPN Jones PO Box 232, Pietersburg (015)2115432
Entity
Records 2213S CD Johnson 34 Memory Road, Cape Town (021)2212321
231A JP Stopforth 11 Church Street, Bloemfontein (051)5312345
CUSTOMERS table with some data

Relationships express real-world associations between entities, e.g. CUSTOMER-BUYS-PRODUCT.


This relationship is a many-to-many relationship because one customer can buy several products
and one kind of product can be bought by more than one customer. We will consider the
relationship SUPPLIER-SUPPLIES-PRODUCT to be a one-to-many relationship because we assume
that a supplier can supply more than one product but a product can be supplied by one
supplier only. The table on the one-side of a one-to-many relationship is called the primary
table and the table on the many-side is called the secondary table.

One-to-many relationships are implemented in a relational database by means of foreign keys.


When the key of one table appears in another table as a non-key field, it is called a foreign key
in the second table. Foreign keys link the records in two tables. In the example tables below,
Supplier code, the primary key of SUPPLIERS, appears as foreign key in PRODUCTS. This way
it is possible to determine that sweaters and tracksuits are supplied by John's Wholesalers and
that Mahew supplies trousers.

SUPPLIERS
Supplier code Name Address Telephone
12 John's wholesalers 17 Breyton road 345 1123
11 Edmund clothing PO Box 13 112 4536
23 Mahew PO Box 3323 112 2234
1
Primary key SUPPLIER-SUPPLIES-PRODUCT
PRODUCTS Many
Product code Description Price Supplier code
A1 Sweater 135.56 12
A2 Track suit 250.34 12
A3 Trousers 112.45 23
Foreign key

Many-to-many relationships cannot be implemented directly in a relational database. We have


to create a separate, connecting, table and replace the many-to-many relationship with two
one-to-many relationships. For the CUSTOMER-BUYS-PRODUCT relationship, we have to create
the table SALE and two one-to-many relationships, CUSTOMERS-SALE and PRODUCTS-SALE. Note
that SALE has a compound key that consists of the keys of the linked tables in the many-to-
many relationship (Customer number, Product code). This connecting table may have non-
key fields, e.g. an invoice number on which the sale was done.

SALE
Customer number Product code Invoice number
112W3 A1 1
112W3 A2 1
112W3 A3 1
2213S A1 2
2213S A2 3
231A A3 4
Chapter 14 4 Databases

Create a database in Microsoft Access


Create the tables

Create a new database file, Clothing.accdb.

Click on Create / Table Design


- Fill in the field names for the CUSTOMERS table above. Accept the default data type will
be Text.
- Right-click on the CustomerNumber field and select Primary Key.
- Right click on the tab with the new table and click on Save. Enter the table name,
CUSTOMERS, and click OK.

Repeat the process for the other tables, SUPPLIERS, PRODUCTS and SALE.
- The data type for all fields may be text, except for the PRODUCT/Price field which must
be Number / Decimal.

Relationships and referential integrity

Because a database can include a large number of entities and relationships, database
designers often use an entity relationship diagram (ERD) to document a database's
structure. In Microsoft Access, all of the above relationships can be defined and
represented as follows in an ERD.

- Close all the tables. Click on Database Tools / Relationships. Add all the tables to the
ERD.

- Select one or more fields in the primary table with the mouse and then drag the mouse
to the secondary table. A dialog box similar to the one below will appear.
Chapter 14 5 Databases

Referential integrity is a term used to indicate that no values may exist in the secondary
table of a relationship if there is not a corresponding value in the primary table. For
instance, we may not add a new record to SALE if the corresponding product is not yet
registered in PRODUCTS. The reverse also holds: if a record is deleted from PROUCTS, all
records in SALE for that specific product must be deleted as well. Furthermore, if a product
code is changed in PRODUCTS, all occurrences of that product code in SALE must be
changed as well.

- Note the checkboxes in the dialog box above that must be checked to enforce
referential integrity. If Cascade Update is checked, all changes to values in the primary
table will automatically be written to all occurrences of that value in the secondary
table. If Cascade Delete is checked, a deletion of a record in the primary table will
result in the automatic deletion of all related records in the secondary table.
- We mostly check the Cascade Update box, but you must think very carefully about the
Cascade Delete box. This could be disastrous and sometimes we prefer not to check it.
In this case, when the user tries to delete a record in the primary table, he will get a
message that it is not possible and that he/she must first delete all related records in
the secondary tables.

The key fields are marked with and the one-to-many relationships are represented with
1– combinations. It is good practice to set up an ERD such that the one-to-many
relationships are read from left to right.

ER diagram
Chapter 14 6 Databases

Queries and SQL

Suppose the shop manager needs the following information from the database:

Q1: For the purposes of a catalogue, the manager needs an alphabetical list of all the
products with their codes, descriptions and prices. Only products with a price of R200
or less must be listed.
Q2: The manager needs a list of all product details, including the respective supplier names
and telephone numbers.
Q3: The manager receives a query from MPN Jones and needs a list of all the product
descriptions and the respective supplier names for all the products bought by him.
Q4: What is the total amount on invoice 1?

Given tables with a relative small amount of data, a human can answer the above queries by
searching the tables manually. This is, however, not always the case and we need a way to
query the database programmatically. For this purpose, a query language, called SQL
(Structured Query Language) (some pronounce it "es kew el", others pronounce it as "sequel")
was developed. SQL is a platform independent language that uses the same syntax
irrespective of the application. This means that the same SQL statement that works in Access
can be embedded in a C# application as well (beware, there are small syntactical catches).

A single table query

Create a new query in your Access database.


- Click on Create / Query Design.
- Close the dialog box that appears.
- Right click in the query window and select SQL view.

The first question above can be answered by the following SQL statement:

SELECT ProductCode, Description, Price


FROM Products
WHERE Price <= 200
ORDER BY Description

Click on to run the query.

Save the query as qryProduct.

Understand what you are doing

An SQL statement has the following general structure:

SELECT <field names> [,<aggregate functions>]


FROM <table names>
[WHERE <conditions>]
[ORDER BY <field names> [DESC]]
[GROUP BY <field names>]

Note the following:


Keywords are written in capitals.
<> indicates elements that must be listed from the specific database.
[] indicates optional elements.
Chapter 14 7 Databases

Note that if there are one or more spaces in a field name, the field name must be enclosed
in square brackets.

SQL is not case sensitive. In other words:

SELECT ProductCode, Description, Price


FROM Products
is equivalent to

SELECT productcode, description, price


FROM PRODUCTS

Note about the views:


- There are three views available for a query: Design view, Datasheet view and SQL view.
See the View item on the main menu.
- It is possible to design a query in Design view with drag and drop and then switch over
to SQL. This way, the SQL is generated automatically.
- You will need the SQL when we start to use the queries in C#. Therefore, try to keep
away from design view as much as possible and work in SQL straight away.

Join tables together

Add a new query and enter the following SQL statement to answer the second question
(Q2):

SELECT Products.*, SupplierName, Address, Telephone


FROM Suppliers INNER JOIN Products
ON Suppliers.SupplierCode = Products.SupplierCode

Note the following:


- A * may be used in the SELECT clause to include all the fields from a table.
- If the same field name is used in more than one table, the field name must be preceded
with the table name and a period.
- Since the fields that must be included are from two tables, they must be joined in the
FROM clause. The ON clause specifies the fields from the two tables that must be equal.
Refer to the ERD above again.

The third query (Q3) is a little bit more involved and may look like this:

SELECT Description, SupplierName


FROM ((Customers C INNER JOIN Sale ON C.CustomerNumber=Sale.CustomerNumber)
INNER JOIN Products P ON Sale.ProductCode=P.ProductCode)
INNER JOIN Suppliers S ON P.SupplierCode=S.SupplierCode
WHERE CustomerName="MPN Jones";

Note the following:


- It is always handy to have a printed version of the ERD nearby when you write queries.
- All the table names that are involved in either the SELECT statement or in the conditions
must be joined in the FROM statement.
- Note the use of brackets to combine the different joins together. The brackets are not
always necessary in other database systems such as MySQL.
- Aliases can be defined for table names to make the writing in subsequent references to
the table somewhat less.
Chapter 14 8 Databases

Using aggregate functions

Add a new query and enter the following SQL statement to answer the fourth question.
SELECT SUM(Price) AS TotalPrice
FROM Products P INNER JOIN Sale S ON P.ProductCode = S.ProductCode
WHERE InvoiceNumber = "1"

We can expand the query a bit to list the totals on all invoices:
SELECT InvoiceNumber, SUM(Price) AS TotalPrice
FROM Products P INNER JOIN Sale S ON P.ProductCode = S.ProductCode
GROUP BY InvoiceNumber

Note the following:


- Columns can be named with an appropriate alias after the keyword AS.
- All fields that are not included in the aggregate function(s) must be included in a GROUP
BY clause.
- Aggregate functions can be used to determine various statistics on data fields. Other
functions that are available are AVG, COUNT, FIRST, LAST, MIN, MAX, SUM and STDEV. See
Microsoft® Access Help for examples of how to use them.

Data access with Visual Studio and C#

The connection between a data source and a data set

A data source is the primary source of data. A data source is mostly a database, but it can
also be a text file, spreadsheet or XML file. A data set is an in-memory cache of data
retrieved from a data source. For performance purposes, chunks of data are kept in a data
set from where queries are run and manipulations are done. We need a way to connect to
a data source, retrieve the data (data source  data set) and update it again (data set 
data source).

ADO.NET is an object-oriented set of libraries contained within the .NET framework that
allows you to interact with data sources. Different providers provide component libraries to
act as "adapter" between the specific database and .NET.

There are four core classes for which providers must provide an interface:

- DbConnection Since a data source is always external to the application; we need a


connection object to connect to the data.
- DbCommand Executes a command against a data source, often in the form of a SQL
statement that retrieves or updates data in the data source.
- DbDataReader Performs a forward-only sequential access of data in the data source.
- DataAdapter A data adapter object serves as bridge between the external data
source and the internal data set for retrieving and saving data. It has
a Fill() method to load data from the data source into the data set
and an Update() method to send changes made to the data set back
to the data source.

Each provider has its own set of components that interacts with these core classes. For the
purposes of connecting to the Microsoft Access database, we will use the components with
prefix OleDb-, for example OleDbDataAdapter. In order to use these classes in your
projects, you need to include the System.Data.OleDb namespace in a using directive.
Chapter 14 9 Databases

The table below lists some of the common providers of ADO.NET components with the
types of data sources that they support.

Provider name Class prefix Data source


Open Database Connectivity Odbc Data sources with an ODBC interface.
Normally older databases.
Object linking and embedding databases OleDb Access or Excel
Oracle Oracle Oracle databases
SQL Server Sql For Microsoft SQL Server
Borland Data Provider Bdp Generic access to various databases, e.g.
Interbase, SQL Server, IBM DB2, Oracle
MySQL MySql MySQL Database

The in-memory cache of data in the data set is encapsulated by a DataSet object. It may
contain multiple DataTable objects, which contain columns and rows, just like normal
database tables. The DataSet object enables manipulation of data in memory which is
much faster than manipulating data directly on an external data source.

Form controls, such as a DataGridView, get their data from one of the data tables in the
data set.
The following connection diagram provides a summary of the various classes and how they
interact with one another:

Database / Data source

Data/table adapter
Update() Fill()
Data set

Form controls, e.g.


BindingNavigator
and DataGridView

Display data in a grid

Start with a new Windows Forms application in Visual Studio, Suppliers.

Put an OleDbConnection object on the form.


- Name it with a cn prefix and a reference to the database, e.g. cnClothing.
- Complete its ConnectionString property to point to the database that you want to
connect to, for example
Provider=Microsoft.ACE.OLEDB.12.0;Data Source="C:\Clothing\Clothing.accdb"

Put an OleDbDataAdapter component on the form.


- A wizard might prompt you for the connection to use. Click Cancel.

Note: Visual Studio provides many wizards for data connections. You may follow the
steps in the wizards, but that would take control out of your hands and you may not
understand what was done behind the scenes. We will use the wizard later.

- Name the data adapter with a da prefix and a reference to the table for which data will
be retrieved, e.g. daSuppliers.
Chapter 14 10 Databases

Add a DataGridView from the Toolbox on the form.


- Name it with a dgv prefix and the name of the table for which you want to retrieve the
data, e.g. dgvSuppliers.
Put a button, named btnFill, on the form.
Include the System.Data.OleDb namespace in a using directive.
In the click event handler of the Fill button:
- Assign the SELECT query for the data that you want to retrieve to the
SelectCommand.CommandText property of the data adapter.
- Assign the OleDbConnection object to the SelectCommand.Connection property of the
data adapter.
- Instantiate a local instance of the DataTable class. We could have used a DataSet as
well, but a DataTable will suffice for our needs and is simpler to handle.
- Call the Fill() method of the data adapter. Provide the DataTable instance as only
parameter.
- Set the DataSource property of the DataGridView object to the DataTable instance.
The following example might help
//Data adapter properties
// - This could also have been done in the Properties window
1 string sql = "SELECT * FROM Suppliers";
2 daSuppliers.SelectCommand.CommandText = sql;
3 daSuppliers.SelectCommand.Connection = cnClothing;
//Create object to store data in memory
4 DataTable tblSuppliers = new DataTable();
//Fill in-memory cache with data from the data source
5 daSuppliers.Fill(tblSuppliers);
//Display data in the data grid view
6 dgvSuppliers.DataSource = tblSuppliers;

If we decided to use a DataSet object to hold the in-memory data, the last three lines in
the code above would have looked like this. Remember that a DataSet object may
represent the entire database whereas a DataTable object represents only one table in the
database.
4 DataSet dstClothing = new DataSet();
5 daSuppliers.Fill(dstClothing);
6 dgvSuppliers.DataSource = dstClothing.Tables[0];

Run the program, click on Fill button, and make sure that the data is displayed correctly.
Chapter 14 11 Databases

Understand what you are doing

Keep the general connection diagram above in mind. It will help you to understand what
comes after what and what is connected to what.

In the connection diagram below, the brown arrow indicates the direction of data flow:
from the external data source, through the in-memory DataTable object until it is
presented to the user in a DataGridView.

The black arrows indicate how the different components are connected, i.e. who the owners
are of the respective properties or methods and where they point to:
- The connection object, cnClothing, has a ConnectionString property that connects to
the database.
- The data adapter, daSuppliers, has a SelectCommand.Connection property that points
to the connection. The data adapter also has a SelectCommand.CommandText property
that specifies what data to retrieve from the data source.
- The form control, dgvSuppliers, has a DataSource property that points to one of the
tables of the data set.
- The data adapter has a Fill() method that fills the data set with the data that is
retrieved from the data source. The connection object specified with the Connection
property must be valid, but it does not need to be open. If the connection is closed
when Fill() is called, it is opened to retrieve data, then closed. If the connection is
open before Fill() is called, it remains open.

Direction tblSuppliers
of data Fill() DataSource

daSuppliers dgvSuppliers
SelectCommand

Connection

cnClothing

ConnectionString

Data source
(Clothing.accdb)

Resources

General
http://en.csharp-online.net/Working_with_Data%E2%80%94ADO.NET_Classes

Data sets
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_raddata/html/ee57f4f6-9fe1-4e0a-be9a-
955c486ff427.htm

DataGridView
http://en.csharp-online.net/Working_with_Data%E2%80%94Using_the_DataGridView
http://dotnetperls.com/datagridview-tutorial
http://www.switchonthecode.com/tutorials/csharp-tutorial-binding-a-datagridview-to-a-
database
Chapter 14 12 Databases

Update data from a grid

Remember that the DataGridView displays data from the in-memory cache (data set) of
the database (data source). In other words, changes in the DataGridView must be
explicitly committed to the database.

We use the data adapter component again to act as bridge between the actual database
and the in-memory copy thereof. In order to update, insert or delete records, we have to
define the UpdateCommand, InsertCommand and DeleteCommand properties of the data
adapter and then call the Update() method. Each one of these properties refers to an
OleDbCommand object with, in turn, a number of properties, the most important of which
are the Connection and CommandText properties.

The CommandText properties of the UpdateCommand, InsertCommand and DeleteCommand


objects can be quite intricate, but fortunately, most of the ADO.NET providers provide a
class, OleDbCommandBuilder, (in the System.Data.OleDb namespace) that can be used to
generate the SQL for the respective CommandText properties, based on the SELECT
command of the data adapter. For example:

OleDbCommandBuilder cmdBuilder = new OleDbCommandBuilder(daSuppliers);


daSuppliers.InsertCommand = cmdBuilder.GetInsertCommand();
daSuppliers.UpdateCommand = cmdBuilder.GetUpdateCommand();
daSuppliers.DeleteCommand = cmdBuilder.GetDeleteCommand();

Note that an OleDbCommandBuilder object can only generate commands for SELECT queries
that are based on single tables. In other words, JOINs are not supported.

Just for interesting sake: A typical update command for a Microsoft Access database might
look like this:

UPDATE Suppliers
SET SupplierCode = ?, SupplierName = ?, Address = ?, Telephone = ?
WHERE ((SupplierCode = ?)
AND ((? = 1 AND SupplierName IS NULL) OR (SupplierName = ?))
AND ((? = 1 AND Address IS NULL) OR (Address = ?))
AND ((? = 1 AND Telephone IS NULL) OR (Telephone = ?))
)

The contents of the in-memory data cache as displayed by the DataGridView can be
retrieved by referencing the DataSource property of the DataGridView. This property
returns an object of the generic Object class and we have to cast it to a DataTable object:

DataTable tblSuppliers = (DataTable)(dgv.DataSource);

The Update() method of the data adapter calls the respective INSERT, UPDATE, or DELETE
statements for each inserted, updated, or deleted row in the specified DataTable, for
example:

daOrders.Update(tblSuppliers);

It is quite possible that the user could have entered some invalid data, e.g. a string where
a number was expected or a duplicate key value. It is, therefore, essential to add some
error handling as well. An example of the entire update procedure is given below as
defined in an Update() method:
Chapter 14 13 Databases

private void Update()


{
//Obtain SQL command for the respective command objects of the data adapter
OleDbCommandBuilder cmdBuilder = new OleDbCommandBuilder(daSuppliers);
daSuppliers.InsertCommand = cmdBuilder.GetInsertCommand();
daSuppliers.UpdateCommand = cmdBuilder.GetUpdateCommand();
daSuppliers.DeleteCommand = cmdBuilder.GetDeleteCommand();

//Obtain in-memory data from the DataGridView


//- The DataTable object in the Fill() event handler above was local and
// we don't have access to it here
DataTable tblSuppliers = (DataTable)(dgvSuppliers.DataSource);

//Make provision for invalid user entries


try
{
//Update data source with data from the in-memory cache
daSuppliers.Update(tblSuppliers);
}
catch (Exception error)
{
MessageBox.Show(error.Message);
//Fill the data again from the data source
//- overwriting user's changes
tblSuppliers.Clear();
daSuppliers.Fill(tblSuppliers);
}
}

If you want the user to click a Save or Update button explicitly to commit the changes in
the data set to the database, you can call the Update() method from a button's Click
event handler. If you are worried that users will forget to click the button, you can call the
Update() method from the Validating event handler of the DataGridView. This event
occurs as part of a series of events whenever a control loses or obtains focus.
- It is important that you don't use both event handlers as this might cause concurrency
errors.

Use a typed DataSet

Typed datasets have a structure that imitates that of the data source. In other words, the in-
memory cache of data will consist of tables with columns as in the original data source.
Tables, columns and relationships can be instantiated in the code with direct reference to a
class that inherits from the base class, DataSet, and contains specific information about the
structure of the selected database.

Start with a new project in Visual Studio, Clothing. We will gradually expand this project to
serve as example of several database functionalities.
- Rename the form as frmSuppliers.
- Add a Close button.

Before you do anything else:

- Run the program so that the full directory structure will be created.
- Put the database that you are going to work with in the "\bin\Debug" folder.
Chapter 14 14 Databases

Add a typed data set class for the project:


- Click on Data / Add New Data Source …
- Select Database in the Data Source Configuration Wizard and click Next.
- Click New Connection and select the Clothing database in the "\bin\Debug" folder. Click
Next.
- You will be asked if you want to copy the file to your project. Click No.
If you click Yes, the database will be copied to the project folder (the one which also contains
the ".csproj" file). When you run the application another copy will be made in the
"\bin\Debug" or "\bin\Release" folders. Changes to the structure in the database should be
made to the one in the project folder since the one in the "\bin" folder will be overwritten
time and time again. During run-time the one in the "\bin" folder will be updated, meaning
that all changes will be lost of you close and rerun you program from within Visual Studio.
If you click No, no copies will be made and you will work with the original one wherever it
was saved. In this case you will have to make provision that the user can change the path
since it is highly unlikely that the file structure on his computer will be the same as on yours.
The best option is to copy the database directly into the "\bin" folder before you create a new
connection object. Then adjust the connection string so that it does not refer to any path,
meaning the system will always look for it in the folder where the ".exe" file resides.

- You may save the connection string. Click Next.


- You will now get a dialog box as in the screen print below.

- Select all tables in the database.


- You can keep the DataSet name as suggested by the wizard or you can change it, as
long as you remember that this is a class and not an instance. It might be a good idea
to change the DataSet name to CClothingDataSet.
- Click Finish.

Click on Data / Show Data Sources to show a hierarchical view of the data set with tables
and fields.
Right click anywhere in the Data Sources window and select Edit DataSet with Designer.
Chapter 14 15 Databases

- The Dataset Designer is a set of visual tools for creating and editing typed datasets and
the individual items that make up datasets. The Dataset Designer provides visual
representations of the objects contained in typed datasets. You create and modify table
adapters, table adapter queries, DataTables, DataColumns, and DataRelations with
the Dataset Designer.
(ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_raddata/html/cd0dbe93-be9b-41e4-bc39-
e9300678c1f2.htm)

- Note that a similar ERD as the one that was created for the data source in Microsoft
Access has now been generated for the data set.

- The symbols for the one-to-many relationships has been replaced with .
- The tables on the many-sides of the relationships are on the left and the ones on the
one-side are on the right.

Add an OleDbDataDapter from the Toolbox to frmSuppliers.

- Select the database that was used for the typed data set in the Data Adapter
Configuration Wizard. Click Next.

- Check that the Use SQL statements radio button is checked and click Next.

- Type a basic SELECT statement (SELECT * FROM Suppliers) in the text box and click
Next.

- Be glad that the wizard will now auto-generate all the SQL statements for the
CommandText properties of the InsertCommand, UpdateCommand and DeleteCommand
objects and that you don't have to use an OleDbCommandBuilder object as above. Click
Finish.

- You will notice that the wizard added an OleDbConnection object as well.

- Rename the connection and data adapter objects to cnClothing and daSuppliers
respectively.
Chapter 14 16 Databases

- Inspect the properties of both these components and check that you understand what
the wizard did. Check especially
cnClothing.ConnectionString
daSuppliers.SelectCommand.Connection
daSuppliers.SelectCommand.CommandText
daSuppliers.UpdateCommand.Connection
daSuppliers.UpdateCommand.CommandText
daSuppliers.InsertCommand.Connection
daSuppliers.InsertCommand.CommandText
daSuppliers.DeleteCommand.Connection
daSuppliers.DeleteCommand.CommandText

Add a DataSet component from the Toolbox to the form.


- Check the Typed dataset radio button and click OK.
- Rename the component to dstClothing.
- Important: Don’t confuse the DataSet instance in this form and the class that was
defined previously with the Data Source Configuration Wizard. Inspect the Name and
DateSetName properties. The name of this instance is dstClothing but the class that is
available for the entire project is CClothingDataSet. This class is derived from the
generic DataSet class.

Add a DataGridView to the form. Rename it as dgvSuppliers.

- Set the DataSource property of the DataGridView to dstClothing and the DataMember
property to SUPPLIERS.

Important: Use the instance on the form. Don’t' use the project data source which is a
class. If you do that, Visual Studio will instantiate some instances on your behalf and
also auto-generate some code in the form's Load() event handler. If you did it
accidentally, just delete the components that VS added as well as the code in the form's
Load event handler.

Call the Fill() method of the data adapter from the form's Load() event handler to fill the
DataGridView with data:

private void Clothing_Load(object sender, EventArgs e)


{
daSuppliers.Fill(dstClothing);
}

Run the program and check that it displays the data.


To summarise, there is not much that is different from what we did before:
- The Data Source Configuration Wizard did some work for us in creating a class for a
typed data set, CClothingDataSet.
- We added a DataSet component, dstClothing, from the Toolbox instead of
instantiating a DataTable object in the code. This component is an instance of
CClothingDataSet which represents the entire data base and not a single table only.
- Upon adding an OleDBDataAdapter to the form, VS generated the CommandText
properties of the UPDATE, INSERT and DELETE SQL statements so that we don't have to
do that with an OleDbCommandBuilder in the code.
- Besides a DataSource property, we must also specify the DataMember property for the
DataGridView to indicate which of the tables in the data set will be displayed. Note
that you cannot change this to another table in the database and expect to see its data
without also changing the SELECT statement in the data adapter (or using another data
adapter with another SELECT statement).
Chapter 14 17 Databases

Use a BindingSource

Add a BindingSource component from the Toolbox to the form.

- Rename it with a bs prefix and reference to the table that will be connected, e.g.
bsSupplier.
- Set the DataSource property to dstClothing. Don't use the project data source.
- Set the DataMember property to SUPPLIERS. Visual Studio will add a table adapter
component to the form as well as some code in the form's Load() event handler.
Remove all of this – we have a data adapter that we will use.
- Change the DataSource property of the DataGridView to bsSupplier and remove its
DataMember property.
Run the program and check that it displays the data.

What's the difference?

The BindingSource provides currency management, change notification, and other


services between controls on a form and a data source. This is accomplished by setting the
DataSource property to a valid DataSet (that has been filled from a data source). You
then bind controls to the BindingSource. All further interaction with the data is
accomplished with calls to the BindingSource component. Navigation and updating of the
data source is accomplished through methods such as MoveNext(), MoveLast(), and
Remove(). Operations such as sorting and filtering are handled through the Sort and
Filter properties.
In other words, the BindingSource comes between the form control and the data set:

Direction dstClothing
of data Fill() DataSource

daSuppliers bsSuppliers
SelectCommand
DataSource

Connection dgvSuppliers

cnClothing

ConnectionString

Data source
(Clothing.accdb)

Table adapters

Database specific table adapters allow us to get faster access to the database but at the cost
of less control. As an example, let us leave the Clothing project for a while and quickly
develop a single form for the CUSTOMERS table.

Start with a new project, "Customers".


Add a data source to the project as before. Name the representing data set class,
CClothingDataSet.
Chapter 14 18 Databases

Add a DataGridView to the form.

- Rename it as dgvCustomers.
- Click on the component's Task Menu at the top-right corner.
- Select CUSTOMERS from the CClothingDataSet class.

Three components are added to the form automatically. Visual Studio names the instances
with the same name as the corresponding class names, except that it starts with a small
letter. Instances may even have the same name as the classes.
- Rename the DataSet component to dstClothing.
- Rename the BindingSource component to bsCustomers.
- Rename the table adapter to taCustomers.

The following code was entered automatically in the form's Load() event handler:
private void Clothing_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data ...
this.taCustomers.Fill(this.dstClothing.CUSTOMERS);
}

- The Fill() method of a table adapter does not take a DataSet object as parameter as
a DataAdapter does. Instead, it takes an instance of a typed DataTable class, in this
case CClothingDataSet.SUPPLIERDataTable.

Run the program. The data of the CUSTOMERS table is displayed and you did not write a
single line of code yourself! It sounds like a bargain but it comes at a price.

Open the Dataset Designer (Right-click in the Data Sources window and select Edit DataSet
with Designer).
- Note that each table has a table adapter that fulfils the role of a customised (or typed)
data adapter. The table adapters do not inherit from a base TableAdapter class in
.NET. (In fact, there is no TableAdapter class.) Upon generation of the typed dataset
Visual Studio creates a class that is specific for every table in the data set. It
encapsulates private data members for a OleDbDataAdapter, OleDBConnection and
OleDbCommand. Click on a table adapter and inspect the properties – you will see the
familiar properties of an OleDbConnection and OleDbDataAdapter.
Chapter 14 19 Databases

Have a look at the design of frmCustomers again. Note that there are no OleDbConnection
or OleDbDataAdapter components since these are encapsulated (built into) the customised
table adapter, taCustomers. The connection diagram below illustrates this.

Direction dstClothing.CUSTOMERS
of data Fill() DataSource

bsCustomers
DataAdapter

taCustomers DataSource

dgvCustomers
Connection

ConnectionString

Data source
(Clothing.accdb)

The biggest disadvantage of table adapters is that the SelectCommand, DeleteCommand,


InsertCommand and UpdateCommand properties are declared as private and we can only
access them from the Dataset Designer, meaning that they are not accessible from within
the program code.

- It is possible to add queries to a table adapter in the Dataset Designer and then use
parameters for the WHERE clause of a SELECT statement in the code. That's a bit
awkward though, and it is easier to use a data adapter to fill a data set.

- We can also hack the Microsoft code a bit (see the reference below) to provide a public
interface for the SelectCommand, but we will leave that to those of you who like to
meddle on the inside of things.

References

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_raddata/html/a87c46a0-52ab-432a-a864-9ba55069f9eb.htm
http://www.codeproject.com/KB/database/TableAdapter.aspx

Additional functionality
Important note: The additions that are discussed below are done to illustrate principles. The
idea is not to develop a full fledged application.

Set up the application structure

Let us return to the Clothing project and add some functionality. We will develop a Multiple
Document Interface (MDI) with a main form that contains a menu structure from where other
forms can be opened.

Open the Clothing project again.


Chapter 14 20 Databases

Add a new form to the project that will act as MDI parent.
- Click on Project / Add Windows Form … . Select Windows Form.
Don't use the built in MDI Parent Form option since it has a lot of components and code
that we will not use and will be in our way.
- Rename the new form as frmMain.cs.
- Set the IsMDIContainer property to true.
- Change the Text property to CLOTHING SHOP.

Add a MenuStrip to the form.

- Add File, Windows and Help menu items. Note the shortcut keys as indicated by the
underlined characters.
- Add sub-menu-items File/Suppliers, File/Customers and File/Exit.
- Give appropriate names to each menu item and sub-menu tem, e.g. mnuFile,
mnuFileExit, etc.

Double click on the File/Exit menu item and enter the code to exit the program:
private void mnuFileExit_Click(object sender, EventArgs e)
{
this.Close():
}

Open the Program.cs file and change the Main() method to run this form instead of the
Suppliers form:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
}

Enter the following code for the Click() event handler of the Suppliers menu item:
private void mnuFileSuppliers_Click(object sender, EventArgs e)
{
Form frmChild = new frmSuppliers();
frmChild.MdiParent = this;
frmChild.Text = "Suppliers";
frmChild.Show();
}

Run the program and make sure that it works as expected.

Anticipate user mistakes

Users are human. Humans make errors. Errors must be handled. If a user enters a
duplicate value for the supplier code he will get a run-time error, unless:

private void dgvSuppliers_DataError(object sender,


DataGridViewDataErrorEventArgs e)
{
MessageBox.Show(e.Exception.Message, "SUPPLIERS ERROR",
MessageBoxButtons.OK, MessageBoxIcon.Error);
bsSuppliers.CancelEdit();
e.ThrowException = false;
}
Chapter 14 21 Databases

Currency management

Enter the following code in the ListChanged() event handler of the BindingSource
control.
private void bsSupplier_ListChanged(object sender, ListChangedEventArgs e)
{
this.daSuppliers.Update(this.dstClothing.SUPPLIERS);
}

- The BindingSource has a List property that contains an ArrayList with references to
each row in the data set. Each row is an instance of the DataRowView class.
- The ListChanged event occurs when an item in the underlying list, i.e. a row in the
data set, changes.
- If the external data source is updated in this event handler it means that the internal
data cache and the external data source will stay synchronised. This is called currency
management.

Sort the data

Add two radio buttons, radSortCode and radSortName, which will allow the user to specify
the sort order of records. Enter the following code for the CheckedChanged() event
handler of radSortCode. Set the CheckedChanged event of radSortName to point to the
same event handler.
private void radSortCode_CheckedChanged(object sender, EventArgs e)
{
if (radSortCode.Checked)
bsSuppliers.Sort = "SupplierCode";
else
bsSuppliers.Sort = "SupplierName";
}

- The BindingSource component has a Sort property that sets the column name.

Note that this code was actually unnecessary since the user can click on the column
headers in the DataGridView to sort the data according to the selected column.

Navigate through the data set

Add a BindingNavigator to the form.


- Rename it to bnSuppliers.
- Set its BindingSource property to bsSuppliers.
- Set its DeleteItem property to (none) and enter the following code for the Click event
handler of the Delete button:
private void bindingNavigatorDeleteItem_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Sure you want to delete this entry?",
"SUPPLIERS",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == DialogResult.Yes)
bsSuppliers.RemoveCurrent();
}
If you don't do it this way, items will be deleted without asking the user for
confirmation.
Chapter 14 22 Databases

Add a button to the BindingNavigator.

- Set its Name property to tstbtnSave.


- Set its Text property to Save. This text will be displayed as Tool Tip when the user
hovers his mouse over the button.
- Set an appropriate image. (You will find images under
C:\Program Files\Microsoft Visual Studio 9.0\Common7\VS2008ImageLibrary\1033\VS2008ImageLibrary)
For your convenience, there are also some images available on the Student CD that
accompanies this book.
- The DataGridView has an EndEdit() method that can be used to save edits to the data
set. The BindingSource also has an EndEdit() which saves changes to the data
source. Enter the following code for the Click event handler of the button:

private void tstbtnSave_Click(object sender, EventArgs e)


{
try
{
dgvSuppliers.EndEdit();
bsSuppliers.EndEdit();
}
catch (Exception error)
{
MessageBox.Show(error.Message, "SUPPLIERS",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

Find a record

Add a text box, txtFind, which will allow the user to type a supplier name. Enter the
following code in the TextChanged event handler of the text box.

private void txtFind_TextChanged(object sender, EventArgs e)


{
int i = bsSuppliers.Find("SupplierName", txtFind.Text);
if (i >= 0)
bsSuppliers.Position = i;
}

- The BindingSource component has a Find() method that returns the index of the item
in the list with the specified property name and value.
- The Find() method will only return an index >= 0 if there is an exact match, including
case. If you want to search on substrings, more work would be necessary, for example:

private void txtFind_TextChanged(object sender, EventArgs e)


{
int i = 0; bool isFound = false;
while ((i < bsSuppliers.List.Count) && (!isFound))
{
DataRowView row = (DataRowView)bsSuppliers.List[i];
string sValue = row["SupplierName"].ToString().ToUpper();
string sFind = txtFind.Text.ToUpper();
if (sValue.IndexOf(sFind) >= 0)
isFound = true;
else
i++;
}
if (isFound)
bsSuppliers.Position = i;
}
Chapter 14 23 Databases

Filter the data

Add a text box, txtFilter, which will allow the user to type a supplier code or name or
part thereof. Enter the following code in the TextChanged event handler of the text box.
private void txtFilter_TextChanged(object sender, EventArgs e)
{
string sql = "SELECT * FROM Suppliers "
+ "WHERE (SupplierCode LIKE '%" + txtFilter.Text + "%') "
+ " OR (SupplierName LIKE '%" + txtFilter.Text + "%')";
daSuppliers.SelectCommand.CommandText = sql;
dstClothing.SUPPLIERS.Clear();
daSuppliers.Fill(dstClothing);
}

- Note that we had to clear the dataset and refill it from scratch with the filtered data.

Parent-Child relationships

Add another OleDbDataAdapter, BindingSource and DataGridView to the form.

- Enter the basic SELECT statement for the data adapter: "SELECT * FROM Products".
- Rename the data adapter to daProducts.
- Rename the BindingSource to bsProducts, set its DataSource to dstClothing and
DataMember to PRODUCTS.
- Remove the table adapter and associated code that Visual Studio added.
- Rename the DataGridView to dgvProducts and set its DataSource property to
bsProducts.

Your form might look like this now:


Chapter 14 24 Databases

Add the following code to the CurrentChanged event handler of bsSuppliers:

private void bsSuppliers_CurrentChanged(object sender, EventArgs e)


{
if (bsSuppliers.Count > 0)
{
string sSupplierCode
= ((DataRowView)bsSuppliers.Current)["SupplierCode"].ToString();
string sql = "SELECT * FROM Products "
+ "WHERE SupplierCode = '" + sSupplierCode + "'";
daProducts.SelectCommand.CommandText = sql;
dstClothing.PRODUCTS.Clear();
daProducts.Fill(dstClothing);
}
}

- The Current property of the BindingSource returns the current item in the list. It
returns a generic Object and must be cast to a DataRowView object.
- The CurrentChanged event occurs whenever the Current property changes, i.e. when
another row in the data set becomes active.
- In other words, the list of products is updated whenever the current supplier changes.
The WHERE clause in the SELECT statement for the data adapter refers to the foreign
key, SupplierCode. A look at the ERD at the beginning of the chapter might clear up
any confusion in your mind.

The ListChanged event of the BindingSource for the secondary table will be used to
update any changes to the secondary table:

private void bsProducts_ListChanged(object sender, ListChangedEventArgs e)


{
try
{
daProducts.Update(dstClothing);
}
catch (Exception error)
{
MessageBox.Show(error.Message, "PRUDUCT UPDATE",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

As always, we need to prevent run-time errors:

private void dgvProducts_DataError(object sender,


DataGridViewDataErrorEventArgs e)
{
MessageBox.Show(e.Exception.Message, "PRODUCTS ERROR",
MessageBoxButtons.OK, MessageBoxIcon.Error);
bsProducts.CancelEdit();
e.ThrowException = false;
}

Run the program and check that it works as expected.


- Check also that referential integrity is maintained if the supplier code is changed in the
primary table, if a supplier is removed in the primary table or if a product is added with
an invalid supplier code.
Chapter 14 25 Databases

Data bound text boxes

Add a new form to the project, frmCustomers.

- Set its FormBorderStyle property to FixedDialog


- Set its StartPosition property to CenterScreen.
- Put a Close button (with code) on it.

Enter the code for the Click() event handler of the Customers menu item of the MDI
Parent form (Refer to the code for the Suppliers menu item).
Run the program and check that the empty form opens and that you can close it again.
Add the following data bound controls to the form:
- Add a DataGridView to the form. Use the Task menu and select the CUSTOMERS table
from the Project Data Source. Edit the columns so that only the CustomerName field is
displayed.
- Rename the DataGridView and the controls that were added automatically:
dgvCustomers, dstClothing, bsCustomers, taCustomers.
- Add a BindingNavigator to the form. Rename it to bnCustomers and set its
BindingSource property to bsCustomers.
- Add text boxes and labels to the form so that the form looks as in the screen print
below. Use a RichTextBox control for the Address field to enable multiple lines with
line breaks.
- Set the DataBindings.Text property of each text box to refer to the appropriate fields
of the BindingSource. Don't use the Project Data Source CCustomerClothing.
- Set the TabIndex properties so that controls will be accessed in order with the Tab key.

Enter the code to enable currency control:


private void bsCustomers_ListChanged(object sender, ListChangedEventArgs e)
{
taCustomers.Update(dstClothing.CUSTOMERS);
}

Ensure that the user can start typing in the CustomerNumber text box as soon as he clicks
on to add a new customer.
private void bsCustomers_AddingNew(object sender, AddingNewEventArgs e)
{
txtNumber.Focus();
}
Chapter 14 26 Databases

Add a button to the BindingNavigator to save changes to the data set. Enter the
necessary code in its Click() event handler.

try
{
bsCustomers.EndEdit();
dgvCustomers.Invalidate();
}
catch (Exception error)
{
MessageBox.Show(error.Message, "CUSTOMERS",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}

- Since we entered the data outside the DataGridView in the data bound text boxes, we
need to refill the DataGridView with Invalidate().

Enter code for the Delete button on the BindingNavigator to get confirmation from the
user as on the Suppliers form.
Enter the code for the DataError event hander of dgvCustomers as for dgvSuppliers.

Run the program and make sure that everything works as expected.

Reporting by means of a DbDataReader

A DbDataReader object allows us to loop sequentially through the query results. It is a


forward-only reader, meaning that one record is read after the other without skipping anyone
or returning to earlier records. It reads directly from the database and stores only one row at
a time in memory. It is especially good to retrieve a large amount of data.
Add a menu item, Reports, next to File on the MDI parent form. Add sub-menu items for
Customers, Products and Suppliers. Give each menu item an appropriate name.

Enter the following code for the Click event handler of the Customers item:

private void mnuReportsCustomers_Click(object sender, EventArgs e)


{
1 string sConnection = "Provider=Microsoft.ACE.OLEDB.12.0;"
+ "Data Source='Clothing.accdb'";
2 using (OleDbConnection cnClothing = new OleDbConnection(sConnection))
{
//Get data
3 cnClothing.Open();
4 string sql = "SELECT * FROM Customers";
5 OleDbCommand cmd = new OleDbCommand(sql, cnClothing);
6 OleDbDataReader dbReader = cmd.ExecuteReader();

//Build message
7 string sMsg = "";
8 while (dbReader.Read())
{
9 sMsg += dbReader["CustomerNumber"].ToString() + "\t"
+ dbReader["CustomerName"].ToString() + "\n";
10 }
MessageBox.Show(sMsg, "CUSTOMERS");
}
}

Run the program and make sure that you get a message box with the numbers and names
of all customers listed.
Chapter 14 27 Databases

Understand what you are doing

The OleDbDataReader is the OLE implementation of the base class DbDataReader.

The data reader needs a valid and open connection object (lines 1 – 3). Note the use of
the using statement to limit the scope of the connection and ensure that it will be closed
as soon as it goes out of scope.

Interestingly, the OleDbDataReader class does not have a constructor. It has to be


instantiated through a call to the ExecuteReader() method of an OleDbCommand object
(line 6). While the OleDbDataReader is being used (lines 6 – 10), the associated
connection is busy serving it and no other operations can be performed on the connection
other than closing it. If a using statement was not used, an explicit call to the
cnClothing.Close() method would have been necessary after line 10.

The Read() method reads one record at a time (line 8). After each Read() the current
record can be accessed through indexing of the OleDbDataReader object (line 9). Indexes
are either zero-based integers or field names as string values. The Read() method
returns true if there are more records to read; false otherwise. Therefore the while loop
(line 8) will keep on reading records until the last one has been read.

Print to printer

In the example above, each row is concatenated to a string which is eventually displayed
in a message box. You can also adapt the code to print the list to a printer. See Chapter
11 again for explanation of the print procedure.

Add a PrintDocument and a PrintPreviewDialog from the Toolbox to the main form.
Rename them as prntdocReport and prntprvReport respectively.

Replace the code for the Click event handler of the Customers item with the following:
private void mnuReportsCustomers_Click(object sender, EventArgs e)
{
prntprvReport.Document = prntdocReport;
prntprvReport.FindForm().WindowState = FormWindowState.Maximized;
prntprvReport.ShowDialog();
}

Enter the following code for the PrintPage event handler of the prntdocReport control.

private void prntdocReport_PrintPage (object sender,


System.Drawing.Printing.PrintPageEventArgs e)
{
string sConnection = "Provider=Microsoft.ACE.OLEDB.12.0;"
+ "Data Source='Clothing.accdb'";
using (OleDbConnection cnClothing = new OleDbConnection(sConnection))
{
//Variables to control the printing
float y = 10;
Font font = new Font("Arial", 12);
SolidBrush brsh = new SolidBrush(Color.Black);

//Get data
cnClothing.Open();
string sql = "SELECT * FROM Customers";
OleDbCommand cmd = new OleDbCommand(sql, cnClothing);
OleDbDataReader dbReader = cmd.ExecuteReader();
Chapter 14 28 Databases

//Print headers
font = new Font(font, FontStyle.Underline | FontStyle.Bold);
e.Graphics.DrawString("Number", font, brsh, 10, y);
e.Graphics.DrawString("Name", font, brsh, 100, y);
e.Graphics.DrawString("Address", font, brsh, 300, y);
e.Graphics.DrawString("Telephone", font, brsh, 500, y);
y += font.GetHeight() * 2;

//Print data
font = new Font(font, FontStyle.Regular);
while (dbReader.Read())
{
e.Graphics.DrawString(dbReader[0].ToString(), font, brsh, 10, y);
e.Graphics.DrawString(dbReader[1].ToString(), font, brsh, 100, y);
e.Graphics.DrawString(dbReader[2].ToString(), font, brsh, 300, y);
e.Graphics.DrawString(dbReader[3].ToString(), font, brsh, 500, y);
y += font.GetHeight() * 3;
} //while
} //using
} //method

User-defined reports

For this example we will assume that the user knows the basic syntax of SQL.

Add a new form to the project, frmReportUserDefined.

- Set its FormBorderStyle property to FixedDialog


- Set its StartPosition property to CenterScreen
- Add a RichTextBox control on the form and name it txtSQL.
- Put a Close button (with code) on it.
- Put a Print button on the form (we will enter the code later).

- Add the following default SQL query in the form's Load() event handler (the user will
be able to change it during run-time):

private void frmReport_Load(object sender, EventArgs e)


{
txtSQL.Text = "SELECT Sale.CustomerNumber, C.CustomerName, "
+ "Sale.ProductCode, P.Description, Sale.InvoiceNumber \n"
+ "FROM (Sale INNER JOIN Customers C "
+ "ON Sale.CustomerNumber = C.CustomerNumber) \n"
+ " INNER JOIN Products P "
+ "ON Sale.ProductCode = P.ProductCode \n"
+ "WHERE P.Price > 20";
}
Chapter 14 29 Databases

Add a sub-menu item, User defined query, under the Reports item on the main form. Enter
the code for the Click() event handler of this menu item to create and show the Report
form.
Run the program and check that the form opens and that you can close it again.
Add an OleDbDataAdapter to the form, using the Data Adapter Configuration Wizard.
- Enter any valid query for the SELECT statement.
- Rename it to daReport.
- Rename the accompanying OleDbConnection to cnClothing.
Add a PrintDocument and a PrintPreviewDialog from the Toolbox to your form. Rename
them as prntdocReport and prntprvReport respectively.
Enter the following code for the Click() event handler of the Print button:
private void btnPrint_Click(object sender, EventArgs e)
{
prntdocReport.DefaultPageSettings.Landscape = true;
prntprvReport.Document = prntdocReport;
prntprvReport.FindForm().WindowState = FormWindowState.Maximized;
prntprvReport.ShowDialog();
} //btnPrint_Click

Enter the following code for the PrintPage() event handler of the prntdocReport control.

private void prntdocReport_PrintPage


(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
//Variables to control the printing
float x = 10, y = 10;
Font font = new Font("Arial", 12);
SolidBrush brsh = new SolidBrush(Color.Black);

//Get data
daReport.SelectCommand.CommandText = txtSQL.Text;
DataTable tblReport = new DataTable();
daReport.Fill(tblReport);
int nFields = tblReport.Columns.Count;

//Print headers
font = new Font(font, FontStyle.Underline | FontStyle.Bold);
for (int c = 0; c < nFields; c++)
{
e.Graphics.DrawString(tblReport.Columns[c].Caption,
font, brsh, x, y);
x += 150;
}
y += font.GetHeight() * 2;

//Print data
font = new Font(font, FontStyle.Regular);
foreach (DataRow row in tblReport.Rows)
{
x = 10;
for (int c = 0; c < nFields; c++)
{
e.Graphics.DrawString(row[c].ToString(), font, brsh, x, y);
x += 150;
}
y += font.GetHeight();

}
}
Chapter 14 30 Databases

Study the code above carefully and make sure that you understand everything.
- We used an untyped, local, DataTable object to save the data in memory. Therefore,
we cannot refer to the names of columns and we have to use indexes.
- Note the nested loops to step through the rows of the table and the columns within
each row.
Run the program and check that it works properly.

Keywords
Make sure that you know what each of these items mean or where they are used.

Key:

Concepts : Normal
Classes and Controls : Green, e.g. Color
Properties : Bold, e.g. List
Methods : Bold with brackets, e.g. Remove()
Events : Bold with the word "event", e.g. DataError event
Keywords : Blue, e.g. new

AddingNew event DbDataReader OleDbDataReader


ADO.NET DBE Oracle
Aggregate functions DBMS Primary key
BindingNavigator DELETE (SQL) Primary table
BindingSource DeleteCommand PrintDocument
Cascade deletes DrawString() PrintPage event
Cascade updates End user PrintPreviewDialog
CheckedChanged event Entity Read()
CommandText ERD Record
Connection diagram ExecuteReader() Referential integrity
ConnectionString Field Relational database
Currency management Fill() Relationship
Current Filter Remove()
CurrentChanged event Find() RichTextBox
Data Adapter Configuration
Font Secondary table
Wizard
FontStyle SELECT (SQL)
Foreign key
Data set SelectCommand
GetHeight() SET (SQL)
Data source
INSERT (SQL)
Data Source Configuration SolidBrush
InsertCommand Sort
Wizard Invalidate()
DataAdapter SQL
JOIN (SQL) SQL Server
Database
DataBindings List TabIndex
ListChanged event Table
DataError event
DataGridView MDI Table adapter
DataMember MDI Parent Task menu
DataRow MenuStrip TextChanged event
DataRowView Microsoft Access Typed dataset
MoveLast()
DataSet Untyped dataset
Dataset Designer MoveNext()
UPDATE (SQL)
DataSource MySQL Update()
DataTable OleDb (namespace) UpdateCommand
DB2 OleDBCommand
Validating event
DBA OleDbCommandBuilder
DbCommand OleDbConnection
DbConnection OleDbDataAdapter
Chapter 14 31 Databases

Exercises
1. Consider the Clothing database that was used as example in this chapter. Write SQL
statements for the following queries:

1.1 List all customer details in alphabetical order.


1.2 List all supplier details in alphabetical order of the supplier name.
1.3 List the names and addresses of customers who bought a tracksuit.
1.4 Prepare a query that can be used to print an invoice. All possible details for invoice
number 1 must be listed.
1.5 List the average price of all products.
1.6 List the average price of products per supplier.

2. Consider the following scenario: A large company has several official vehicles. Each
vehicle is assigned to a specific responsible staff member.

2.1 Design a database with structure as


indicated in the ERD. All fields can be
defined as Text except for Year which
must be an integer. Enter some text data
into the database.

2.2 Write an SQL query that will list all staff member details together with details about the
vehicle that has been assigned to each staff member. The records should be displayed
in order of the year model. Vehicles in the same year must be listed according to the
registration number.

2.3 Develop a form in Visual Studio that will show the content of the above-mentioned
query in a grid. Don't use a typed data set and don't use the drag-and-drop wizards.

2.4 Add a combo box to the form that can be used to filter the listing by make of vehicle.
Chapter 14 32 Databases

3. Develop a telephone directory application.

3.1 Design a database in Microsoft Access with a single table that contains three Text
fields: Name, Surname and telephone number.

3.2 Design a user interface as in the example. Don't use a typed data set and don't use a
wizard to provide the following functionalities:
The user may enter any surname or part thereof in the text box. Use the
TextChanged() event handler to update the display as the user types. When for
example, the text box contains the characters "Ch", the entries with surnames Chad
and Chiles should be listed. If the text box is empty, all entries in the database
must be listed.

Add a button that will save all changes made in the grid to the underlying database.

4. A Microsoft Access database, "RaceResults.accdb", is available on the Student CD that


accompanies this book. It contains the results of a half marathon and full marathon.

Develop an interface with which the results can be queried as in the example. You should
use a typed dataset with a binding source.
Chapter 14 33 Databases

Hints:
1. Right-click on the DataGridView, select Edit Columns … / Time / DefaultCellStyle and set
the Format field to HH:mm:ss to display the times properly.
2. Set the form's WindowState property to Maximized. Enter the following code in the
form's Resize() event handler to ensure that the Close button stays in the bottom-
right hand corner:
btnClose.Left = this.Width - btnClose.Width - 16;

5. A Microsoft Access database, "GamesGalore.accdb", is available on the Student CD that


accompanies this book. It contains the names and other attributes of several PC games.

Use a typed dataset and develop an interface with which the data can be filtered as in the
example. The data set should be refreshed when the user clicks on either of the combo
boxes or Clear buttons. Use a typed dataset with a binding source. The combo boxes
should be filled with data from the database in the form's constructor.

Hints:
1. Use the following SQL statement to find all game types in the database:
SELECT DISTINCT Type FROM Games
2. Use the OleDbCommand and OleDbDataReader to fill the combo boxes wit game types
and ratings respectively.

6. A Microsoft Access database, "Transport.accdb", is available on the Student CD that


accompanies this book. It contains a single table, Routes, with route numbers, groupings
and descriptions.
Use a typed dataset and develop an application as in the example that will allow the user
to list routes of a specific grouping. Display the route numbers in a DataGridView and the
other fields in data-bound text boxes. Add a BindingNavigator with a Save button that
will save any edits to the database. Add a Print button that will display a print preview of
the selected data.
Chapter 14 34 Databases

7. Consider the following scenario: The manager of XYZ


Securities uses a database to set up the time table for
security guards to take duty at a specific venue. A
database with some test data is available on the
Student CD that accompanies this book. The ERD
shows the relationships between the tables in the
database.

Develop an interface with which the duties for a


specific security guard can be listed and printed.
Chapter 14 35 Databases

Hint: Create a query in Access, qryDuties, which will be available as part of the typed
data set.

SELECT [Date], [Time], StaffNo, D.VenueNo, V.Name, V.Address


FROM Duties AS D INNER JOIN Venues AS V ON D.VenueNo=V.VenueNo

8. Take the previous exercise a bit further: Develop a full-fledged, menu-drive application
which can be used to add, remove and edit staff details and venues. Then create a facility
with which the scheduling can be done, i.e. assigning specific venues to specific staff
members at a specific data and time. Note that no intelligence is required. You only need
to allow the user to enter the date and time for every duty with a staff and venue number.

Hints:

1. Develop an MDI application with a main form that contains a menu strip.
2. Develop separate forms to add, remove and edit the staff members and venues.
3. Develop a form as in the example below to schedule the duties.
4. The DataSource, DisplayMember and ValueMember properties of the ComboBox could be
helpful.
5. Use the OleDBCommand object to define and execute SQL statements to insert or remove
duties from the database. The grid in the form below is only used to view the duties on
the selected date and no editing is done in it.

You might also like