Be sharp with C# (Chapter 14, Database Access)

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

Transcript of Be sharp with C# (Chapter 14, Database Access)

Page 1: Be sharp with C# (Chapter 14, Database Access)

Chapter Fourteen

14Database 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 usethem to display data in a grid or databound text boxes andupdate data

understand the difference between a data source and a data setas well as the difference between typed and untyped data sets

Key concepts

Database

Entity Relationship Diagram

SQL

ADO.NET

Data sources and Data sets

Page 2: Be sharp with C# (Chapter 14, Database Access)

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.

Page 3: Be sharp with C# (Chapter 14, Database Access)

Chapter 14 3 Databases

Customer number Name Address Telephone

112W3 MPN Jones PO Box 232, Pietersburg (015)2115432

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 112311 Edmund clothing PO Box 13 112 453623 Mahew PO Box 3323 112 2234

PRODUCTS

Product code Description Price Supplier code

A1 Sweater 135.56 12A2 Track suit 250.34 12A3 Trousers 112.45 23

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

Records

Fields

Entity

1SUPPLIER-SUPPLIES-PRODUCT

Many

Primary key

Foreign key

Page 4: Be sharp with C# (Chapter 14, Database Access)

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.

Page 5: Be sharp with C# (Chapter 14, Database Access)

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

Page 6: Be sharp with C# (Chapter 14, Database Access)

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, PriceFROM ProductsWHERE Price <= 200ORDER 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.

Page 7: Be sharp with C# (Chapter 14, Database Access)

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, TelephoneFROM 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, SupplierNameFROM ((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.

Page 8: Be sharp with C# (Chapter 14, Database Access)

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 TotalPriceFROM Products P INNER JOIN Sale S ON P.ProductCode = S.ProductCodeWHERE InvoiceNumber = "1"

We can expand the query a bit to list the totals on all invoices:

SELECT InvoiceNumber, SUM(Price) AS TotalPriceFROM Products P INNER JOIN Sale S ON P.ProductCode = S.ProductCodeGROUP 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.

Page 9: Be sharp with C# (Chapter 14, Database Access)

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 ExcelOracle Oracle Oracle databasesSQL Server Sql For Microsoft SQL ServerBorland Data Provider Bdp Generic access to various databases, e.g.

Interbase, SQL Server, IBM DB2, OracleMySQL 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:

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.

Database / Data source

Data/table adapter

Data set

Form controls, e.g.BindingNavigatorand DataGridView

Fill()Update()

Page 10: Be sharp with C# (Chapter 14, Database Access)

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 memory4 DataTable tblSuppliers = new DataTable();

//Fill in-memory cache with data from the data source5 daSuppliers.Fill(tblSuppliers);

//Display data in the data grid view6 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.

Page 11: Be sharp with C# (Chapter 14, Database Access)

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.

cnClothing

daSuppliers

Connection

dgvSuppliers

tblSuppliers

DataSourceFill()

Data source(Clothing.accdb)

ConnectionString

SelectCommand

Resources

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

Data setsms-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_DataGridViewhttp://dotnetperls.com/datagridview-tutorialhttp://www.switchonthecode.com/tutorials/csharp-tutorial-binding-a-datagridview-to-a-database

Directionof data

Page 12: Be sharp with C# (Chapter 14, Database Access)

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 SuppliersSET 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:

Page 13: Be sharp with C# (Chapter 14, Database Access)

Chapter 14 13 Databases

private void Update(){

//Obtain SQL command for the respective command objects of the data adapterOleDbCommandBuilder 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 hereDataTable tblSuppliers = (DataTable)(dgvSuppliers.DataSource);

//Make provision for invalid user entriestry{

//Update data source with data from the in-memory cachedaSuppliers.Update(tblSuppliers);

}catch (Exception error){

MessageBox.Show(error.Message);//Fill the data again from the data source//- overwriting user's changestblSuppliers.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.

Page 14: Be sharp with C# (Chapter 14, Database Access)

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.

Page 15: Be sharp with C# (Chapter 14, Database Access)

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.

Page 16: Be sharp with C# (Chapter 14, Database Access)

Chapter 14 16 Databases

- Inspect the properties of both these components and check that you understand what

the wizard did. Check especially

cnClothing.ConnectionStringdaSuppliers.SelectCommand.ConnectiondaSuppliers.SelectCommand.CommandTextdaSuppliers.UpdateCommand.ConnectiondaSuppliers.UpdateCommand.CommandTextdaSuppliers.InsertCommand.ConnectiondaSuppliers.InsertCommand.CommandTextdaSuppliers.DeleteCommand.ConnectiondaSuppliers.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).

Page 17: Be sharp with C# (Chapter 14, Database Access)

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:

cnClothing

daSuppliers

Connection dgvSuppliers

dstClothing

DataSourceFill()

Data source(Clothing.accdb)

ConnectionString

bsSuppliers

DataSourceSelectCommand

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.

Directionof data

Page 18: Be sharp with C# (Chapter 14, Database Access)

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.

Page 19: Be sharp with C# (Chapter 14, Database Access)

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.

taCustomers

dgvCustomers

dstClothing.CUSTOMERS

DataSourceFill()

Data source(Clothing.accdb)

ConnectionString

bsCustomers

DataSource

Connection

DataAdapter

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.htmhttp://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.

Directionof data

Page 20: Be sharp with C# (Chapter 14, Database Access)

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;

}

Page 21: Be sharp with C# (Chapter 14, Database Access)

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";

elsebsSuppliers.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.

Page 22: Be sharp with C# (Chapter 14, Database Access)

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;}

Page 23: Be sharp with C# (Chapter 14, Database Access)

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:

Page 24: Be sharp with C# (Chapter 14, Database Access)

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.

Page 25: Be sharp with C# (Chapter 14, Database Access)

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();}

Page 26: Be sharp with C# (Chapter 14, Database Access)

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 message7 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.

Page 27: Be sharp with C# (Chapter 14, Database Access)

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 printingfloat y = 10;Font font = new Font("Arial", 12);SolidBrush brsh = new SolidBrush(Color.Black);

//Get datacnClothing.Open();string sql = "SELECT * FROM Customers";OleDbCommand cmd = new OleDbCommand(sql, cnClothing);OleDbDataReader dbReader = cmd.ExecuteReader();

Page 28: Be sharp with C# (Chapter 14, Database Access)

Chapter 14 28 Databases

//Print headersfont = 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 datafont = 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";

}

Page 29: Be sharp with C# (Chapter 14, Database Access)

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 printingfloat x = 10, y = 10;Font font = new Font("Arial", 12);SolidBrush brsh = new SolidBrush(Color.Black);

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

//Print headersfont = 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 datafont = 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();

}}

Page 30: Be sharp with C# (Chapter 14, Database Access)

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 : NormalClasses 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

ADO.NETAggregate functionsBindingNavigatorBindingSource

Cascade deletesCascade updatesCheckedChanged eventCommandText

Connection diagramConnectionString

Currency managementCurrent

CurrentChanged event

Data Adapter ConfigurationWizard

Data setData sourceData Source Configuration

WizardDataAdapter

DatabaseDataBindings

DataError eventDataGridViewDataMemberDataRowDataRowViewDataSet

Dataset DesignerDataSourceDataTable

DB2DBADbCommandDbConnection

DbDataReader

DBEDBMSDELETE (SQL)DeleteCommandDrawString()

End userEntityERDExecuteReader()

FieldFill()FilterFind()FontFontStyle

Foreign keyGetHeight()

INSERT (SQL)InsertCommandInvalidate()

JOIN (SQL)List

ListChanged event

MDIMDI ParentMenuStrip

Microsoft AccessMoveLast()MoveNext()

MySQLOleDb (namespace)OleDBCommandOleDbCommandBuilderOleDbConnectionOleDbDataAdapter

OleDbDataReader

OraclePrimary keyPrimary tablePrintDocument

PrintPage eventPrintPreviewDialogRead()

RecordReferential integrityRelational databaseRelationshipRemove()RichTextBox

Secondary tableSELECT (SQL)SelectCommand

SET (SQL)SolidBrushSort

SQLSQL ServerTabIndex

TableTable adapterTask menuTextChanged event

Typed datasetUntyped datasetUPDATE (SQL)Update()UpdateCommand

Validating event

Page 31: Be sharp with C# (Chapter 14, Database Access)

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.

Page 32: Be sharp with C# (Chapter 14, Database Access)

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.

Page 33: Be sharp with C# (Chapter 14, Database Access)

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.

Page 34: Be sharp with C# (Chapter 14, Database Access)

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.

Page 35: Be sharp with C# (Chapter 14, Database Access)

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.AddressFROM 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.