CSCI E-143 CertPrep
Code for Reading Database Tables
Fall Term, 2004
The Namespace
The SQL Engine
Error Handling
Read one table, using complete and commented code
Read one table, using compact code
Read one table, using drag-and-drop
Read two tables and set a DataRelation between them
Downloadable Code Samples
Chapter 5: Data Binding
Chapter 6: Consuming and Manipulating Data, 1
CertPrep Home Page
Chapter 6: Consuming and Manipulating Data, 2
The Namespace
For the operations listed below, you must have the following directive
at the top of your module:
using System.Data.SqlClient;
This is for Microsoft's SQL Server.
SQL Server's class names begin with Sql,
for instance, SqlDataAdapter.
Other database engines use namespaces and classes whose names begin with other
prefixes.
Oracle's namespace is System.Data.Oracle,
and its class names begin with Oracle.
Other engines, including Microsoft Access,
use namespace System.Data.OleDb,
and their class names begin with
OleDb.
The SQL Engine
For .NET to handle any SQL requests, the SQL service must be
running in the background.
If it isn't, your SQL code will take forever
to run, and you will finally get an SQL Error like
17: SQL Server does not exist or access denied.
To get the SQL server started, go to
Control Panel / Administrative Tools / Services / MSSQLSERVER
and start it. Once it's running,
there should be an icon on the taskbar for it.
Your machine must of course have an SQL Server engine.
If it doesn't,  you can download MSDE for free.
It's a slightly reduced version adequate for this course.
You need two sites. First,
http://go.microsoft.com/fwlink/?linkid=13962
supplies the MSDE engine itself, which you download and install.
Second, go to
http://www.microsoft.com/sql/msde/downloads/default.asp
and choose "Northwind and pubs Sample Databases".
This site will supply the script to create the "Northwind"
database.
(The "Northwind" database is the one that Kalani and everyone else uses
for examples.
I would say that such use is a legal requirement, except that it's
always used.)
You must download the script and then follow the instructions in the
ReadMe file to load it, by running the
osql program from the command line.
This script for loading the Northwind database
might come in handy if you mess up the Northwind database while playing around
with SQL. As you should.
Error Handling
All of the following SQL code given below is assumed to be surrounded
by error-handling blocks.
The SqlException holds a collection
of SQL errors, and we want to display both these individual
SQL errors and the containing SQL Exception,
which we do here in the code immediately following the loop.
This Exception gives us the line number of the offending operation in the C# program.
try
{
//SQL code
}
catch (SqlException sqlex)
{
//Loop to handle individual SqlErrors in the collection.
//This will give errors as discovered by the SQL engine itself.
foreach (SqlError sqlerr in sqlex.Errors)
{
MessageBox.Show("SqlError:\n\n"
+ sqlerr.Message
+ "\n\nNumber:\t\t" + sqlerr.Number
+ "\n\nLine number\t" + sqlerr.LineNumber
+ "\n\nSource:\t\t" + sqlerr.Source
+ "\n\nClass (Severity):\t" + sqlerr.Class
+ "\n\nProcedure:\t" + sqlerr.Procedure
+ "\n\nServer:\t\t" + sqlerr.Server
+ "\n\nState:\t\t" + sqlerr.State
+ "\n\nType:\t\t" + sqlerr.GetType());
}
//After the loop has listed each message from the SQL engine,
//give the facts for the containing C# SqlException,
//including the line location in the C# program and a stack trace.
MessageBox.Show("SqlException:\n\n" + sqlex.Message
+ "\n\nHelp Link:\t" + sqlex.HelpLink
+ "\n\nTarget:\t\t" + sqlex.TargetSite
+ "\n\nStackTrace:\n\n" + sqlex.StackTrace
+ "\n\nInnerException:\n" + sqlex.InnerException);
}
catch (Exception ex)
{
//Handle all other exceptions
MessageBox.Show("Exception while reading Database:\n\n"
+ ex.Message + "\n\n" + ex.ToString());
}
This handling of a collection of SQL errors is an improvement on Kalani's example
on page 540.
Incidentally, it is very difficult to predict whether a given error will cause
a set of SQL errors or a single exception of another type.
For instance, errors in the SqlConnection string cause both types.
You may want to investigate whether it's worth while listing the individual errors
if there is only one of them. In this case the one SQL message should be
passed to the SQL Exception.
In fact, in my experiments I have yet to see a case where a message given
in an individual SQL error did not appear in the containing SQL Exception.
Even when there were two individual SQL errors, both messages were listed
by the Exception.
If you don't want to put all of this in your code for every
try/catch involving SQL,
here is some code that's a little more compact:
try
{
//SQL code
}
catch (SqlException sqlex)
{
//Handle the collection of individual SQL errors
foreach (SqlError sqlerr in sqlex.Errors)
{
MessageBox.Show("SQL Error:\n\n"
+ sqlerr.Number + ": " + sqlerr.Message);
}
//Handle the SQL Exception
MessageBox.Show("SQL Exception:\n\n" + sqlex.ToString());
}
catch (Exception ex)
{
//Handle all other exceptions
MessageBox.Show("Exception while reading Database:\n\n" + ex.ToString());
}
Read one table, using complete and commented code
Here are the C# statements necessary to read a database table,
in as clear an arrangement as I can manage.
(Distinguish this from the case where you drag things from the Server
Explorer.)
It reads the "Customers" table from the "Northwind" database.
Yes, I know: I omitted the try
and catch blocks.
You should insert the following code into the error-handling blocks given above,
using all of it to replace the single
//SQL Code line.
This code is very similar to Kalani, pages 496-7,
which is his simplest code for reading a single table.
//We choose to use the software for Bill's data base engine.
//Microsoft's SQL Server uses classes that begin with "Sql".
//(Other database engines use classes beginning with "Oracle" or "OleDb".)
//Classes such as DataSet and DataGrid, which are not peculiar
//to one database engine type, are not prefixed with "Sql" or "OleDb".
//Create a Connection object.
SqlConnection conn = new SqlConnection();
//Tell the connection the three things it must know:
// where the database is,
// what it's called, and
// why you're allowed to read it.
conn.ConnectionString =
"data source=(local); initial catalog=Northwind; integrated security=SSPI";
//Create an adapter.
SqlDataAdapter da = new SqlDataAdapter();
//Create a command, here a Select command to read the database table.
//The command is created on the connection, which thus knows about it.
SqlCommand selectCmd = conn.CreateCommand();
//Describe the command.
selectCmd.CommandType = CommandType.Text;
selectCmd.CommandText = "Select * from Customers";
//Attach the command to the adapter. The command is thus attached
//to both the connection and the adapter.
da.SelectCommand = selectCmd;
//Create a DataSet.
DataSet ds = new DataSet();
//Fill the DataSet table from the table in the database.
//The name of the table in the DataSet, here "Custs",
//does not have to match that in the database.
da.Fill(ds,"Custs");
//Drag a DataGrid from the .NET IDE ToolBox onto the Form and size it.
//You might also set Dock to "Fill", and enlarge the grid's display font.
//Tell the DataGrid where its data is.
dataGrid1.DataSource = ds;
//The above statements are enough to get the grid to display the data,
//but you will have to choose the table name at run time.
//To have the table appear automatically, use ONE of the
//following statements, but not both. Note that
//the DataSource is an object, but the DataMember is a string.
//dataGrid1.DataSource = ds.Tables["Custs"];
//dataGrid1.DataMember = "Custs";
I can't remember how exactly I needed to know these lines for the exam,
but you must be familiar with them for the rest of this course.
They can, of course, be expressed in different ways,
as in the following example.
Read one table, using compact code
Here are the fewest number of C# statements necessary to do the same
thing.
Note that the SqlConnectionString
(here, the second argument to the DataAdapter's constructor)
uses different words to do the same thing as the previous example.
SqlDataAdapter da = new SqlDataAdapter(
"Select * from Customers",
"server=.;database=Northwind;trusted_connection=sspi");
DataSet ds = new DataSet();
da.Fill(ds,"Custs");
//(This DataGrid has been created from the ToolBox.)
dataGrid1.DataSource = ds;
And yes, I once again omitted the try
and catch blocks.
Read one table, using drag-and-drop
Start a new C# Windows application.
In the Server Explorer, open a database and drag a table to your desktop.
This gives you a DataAdapter called sqlDataAdapter1.
From the "Data" header of the Toolbox, drag a DataSet to your desktop.
This gives you a DataSet called dataSet1.
From the "General" header of the Toolbox,
drag a DataGrid to your desktop.
Using this DataGrid's Properties window, set its
DataSource
to dataSet1.
Double-click the form (not the DataGrid), and under the
Form1_Load event,
type sqlDataAdapter1.Fill(dataSet1);
Run the application.
Read two tables and set a DataRelation between them
Here are the C# statements necessary to read two tables from the database
and establish a Relation between them in the DataSet, so that they will
display as master and detail in the DataGrid.
//Create a connection and a DataAdapter,
//and read the first table into the DataSet.
SqlConnection conn = new SqlConnection
("data source=(local); initial catalog=Northwind; integrated security=SSPI");
SqlDataAdapter da = new SqlDataAdapter("Select * from Customers", conn);
DataSet ds = new DataSet();
da.Fill(ds,"Customers");
//Create a second DataAdapter and read the second table into the same DataSet.
SqlDataAdapter da2 = new SqlDataAdapter("Select * from Orders", conn);
da2.Fill(ds, "Orders");
//Create the DataRelation between the two tables. Arguments:
// 1) a string describing the relation;
// 2) the column in the parent table which matches
// 3) the column in the child table.
//Once the DataRelation has been created, it must be added
//to the DataSet's "Relations" collection. (Note the different names.)
DataRelation rel = new DataRelation
("Orders for this Customer",
ds.Tables["Customers"].Columns["CustomerID"],
ds.Tables["Orders"].Columns["CustomerID"]);
ds.Relations.Add(rel);
//(You must have a DataGrid on the Form.)
//Tell your DataGrid where its data is.
//Tell it which table to display first.
//This will cause the first DataGrid to display the Customers, and,
//when the "+" sign is pressed for a Customer, to show the descriptive
//name of the Relation, so that when that is pressed in turn,
//the Orders for that Customer will be displayed in the same DataGrid.
dataGrid1.DataSource = ds;
dataGrid1.DataMember = "Customers";
//If you want the Orders to appear in a second DataGrid on the Form,
//you declare that Grid's DataSource as follows.
//Note that the DataMember for this Grid is of the form
// "ParentTable.RelationName"
//This will cause the second DataGrid to display only the Orders for
//the Customer selected in the first DataGrid.
dataGrid2.DataSource = ds;
dataGrid2.DataMember = "Customers.Orders for this Customer";
You must be familiar with this code, especially the declarations of the
DataRelation and of the DataSource and DataMember for the second DataGrid.
Kalani talks about this in chapter 6, on pages 492 and 502-506,
but he displays all database tables in the same data grid,
so he never has to show the setting for DataMember
for a second data grid showing a related table.
I got some sort of question about this, but I can't remember exactly what.
If you're still interested, there are some
downloadable code samples.
Last revised Dec. 5, 2004