Download Notes 5. Working with Multiple Tables

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

SQL wikipedia , lookup

Database wikipedia , lookup

Microsoft SQL Server wikipedia , lookup

Ingres (database) wikipedia , lookup

Microsoft Jet Database Engine wikipedia , lookup

Entity–attribute–value model wikipedia , lookup

Functional Database Model wikipedia , lookup

Clusterpoint wikipedia , lookup

Versant Object Database wikipedia , lookup

Extensible Storage Engine wikipedia , lookup

Relational model wikipedia , lookup

Database model wikipedia , lookup

Transcript
Working with Multiple Tables
So far, all of the examples we’ve done involve working with a database
(topmovies.mdb) with a single table (movieList). Most real databases aren’t that
simple – they have multiple tables with primary/foreign key relationships between them.
In addition, those relationships often have one or more referential integrity constraints.
This has several implications:

Standard referential integrity: This prevents changes that involve deleting records
in the table with the primary key without first deleting all related records in the
foreign key table (standard referential integrity).

Cascading deletes: When a record in the primary key table is deleted, all related
records in the foreign key table are also deleted.

Cascading updates: When a record in the primary key table is updated, relevant
fields in all related records in the foreign key table are also updated.
While this makes working with the database more complicated, it only really affects the
building of the query. Creating the application is really no more complicated with a
multi-table database than a database with a single table. In addition, there are QBE
(Query-by-Example) tools built into Visual Studio 2008 that make it easy to construct the
SQL queries (if you need it).
Example: Order Entry Database
As an example, we’ll write an application to work with a simple database that tracks
orders. The database has three tables. Their schemas are below:
Customer
CustomerID
Long integer
FirstName
String
LastName
String
Street
String
City
String
State
String
Zip
Long integer
Product
ProductID
Long integer
ProductName
String
ProductDescription String
Supplier
String
UnitPrice
Double
Inventory
Long integer
SalesTransaction
OrderID
Long integer
CustomerID
Long integer
ProductID
Long integer
Quantity
Long integer
OrderDate
DateTime
Working with multiple tables
Page 2
There are several primary key/foreign key relationships:
The application we will create will add records to the SalesTransaction table. The final
application will look like this:
Now let’s run through each piece of this application before we build it:
1) Order ID field. This is the ID number associated with the order. This ID must be
unique.
2) Generate OrderID button. This button will generate a unique OrderID. It does this
by finding the highest existing OrderID in the SalesTransaction table and adding 1 to
that value.
3) Customer drop-down list. The drop-down list is populated with the LastName field
of the Customer table (with CustomerID as the “value” for each item).
4) Product drop-down list. The drop-down list is populated with the ProductName
field of the Product table (with ProductID as the “value” for each item). When a
Working with multiple tables
5)
6)
7)
8)
Page 3
product is selected from the list, the gray field on the next line is updated with the
product’s current inventory level (from the Inventory field of the Product table).
Quantity field. This is the amount of the product the customer wants to order. This
should be an integer greater than 0 and less than the available inventory.
“Max” inventory label. This is the amount of the currently selected product left in
inventory.
Order date label. This label contains the current date – this will be the date assigned
to the order.
Submit order button. This button triggers the code that updates the SalesTransaction
table with the new order and the Product database with a modified inventory level.
Before both queries are executed, the order quantity is checked to make sure it is less
than the amount in the inventory.
Create the application
1) In Visual Studio 2008, create a new web site project called OrderEntry.
2) Move the SalesDatabase.mdb file into the project folder and add that file to the
project.
3) Create the user interface by dragging the necessary components onto the form.
Follow the graphic below as a guide (don’t worry about the AccessDataSource
objects yet). Notice that the GUI elements are placed in a 2 by 5 table. The
submitButton and the statusLabel are placed below the table. The left
column of the table is just text typed into the table cell.
Layout for the Order Entry Application
(pay attention to the object names!)
Hyperlink to allOrders.aspx
TextBox called
orderBox
RangeValidator for
orderBox (int between 1
and 1000000)
Button called
generateID
DropDownLists called
customerDropDown
and productDropDown
TextBox called
qtyBox
Label called
dateLabel
RangeValidator for
qtyBox (int between
1 and 1000000)
Button called
submitButton
Label called
statusLabel
Label called
qtyLabel
Working with multiple tables
Page 4
4) Set up the two RangeValidator objects (it doesn’t matter what you call them).
For both of them, the minimum value is 1, the maximum value is 1000000, and the
type is integer. Make sure you set the proper control in the ControlToValidate
setting.
5) Now bind the customerDropDown to the Customer table of
SalesDatabase.mdb by selecting “Choose Data Source” from its Task Tab. Call
the AccessDataSource you create customerSource. The lastName field
should be the display field and the CustomerID field should be the value field.
6) Also bind the productDropDown to the Product table of
SalesDatabase.mdb by selecting “Choose Data Source” from its Task Tab. Call
the AccessDataSource you create productSource. The productName field
should be the display field and the ProductID field should be the value field. Also
make sure the “Enable AutoPostBack” checkbox should be selected in the Task Tab.
7) Move the customerSource and productSource objects to the bottom of the
form. You don’t have to do this (those elements won’t be visible when the application
is run), but it makes it easier to see how to the application will actually look.
8) Now you’ll need to create the code behind the application. The code for the entire
class is below. Look over it and then we’ll go over the highlights of each piece.
using
using
using
using
using
using
using
using
using
System;
System.Data;
System.Configuration;
System.Web;
System.Web.Security;
System.Web.UI;
System.Web.UI.WebControls;
System.Web.UI.WebControls.WebParts;
System.Web.UI.HtmlControls;
public partial class _Default : System.Web.UI.Page
{
DateTime odate;
static string CSTR = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="
+ AppDomain.CurrentDomain.BaseDirectory + "SalesDatabase.mdb";
protected void Page_Load(object sender, EventArgs e)
{
odate = DateTime.Now;
int day = odate.Day;
int month = odate.Month;
int year = odate.Year;
string dateString = day.ToString() + "/" + month.ToString() +
"/" + year.ToString();
dateLabel.Text = dateString;
}
Working with multiple tables
Page 5
protected void generateID_Click(object sender, EventArgs e)
{
string query = "SELECT MAX(OrderID) FROM SalesTransaction";
System.Data.OleDb.OleDbCommand ocmd =
new System.Data.OleDb.OleDbCommand(query,
new System.Data.OleDb.OleDbConnection(CSTR));
ocmd.Connection.Open();
System.Data.OleDb.OleDbDataReader dr = ocmd.ExecuteReader();
dr.Read();
int highestID = dr.GetInt32(0);
ocmd.Connection.Close();
int newID = highestID + 1;
orderBox.Text = newID.ToString();
}
protected int getInventoryLevel()
{
string query = "SELECT Inventory FROM Product WHERE " +
"ProductID=@ProdValue";
System.Data.OleDb.OleDbCommand ocmd =
new System.Data.OleDb.OleDbCommand(query, new
System.Data.OleDb.OleDbConnection(CSTR));
ocmd.Parameters.AddWithValue("@ProdValue",
productDropDown.SelectedValue);
ocmd.Connection.Open();
System.Data.OleDb.OleDbDataReader dr = ocmd.ExecuteReader();
dr.Read();
int quantity = dr.GetInt32(0);
ocmd.Connection.Close();
return quantity;
}
protected void productDropDown_SelectedIndexChanged(object sender,
EventArgs e)
{
int invQuantity = this.getInventoryLevel();
qtyLabel.Text = invQuantity.ToString();
}
protected void submitButton_Click(object sender, EventArgs e)
{
int invQuantity = this.getInventoryLevel();
int orderQuantity = Convert.ToInt32(qtyBox.Text);
if (orderQuantity > invQuantity)
{
statusLabel.Text = "Order quantity exceeds inventory!";
}
else
{
int orderID = Convert.ToInt32(orderBox.Text);
int customerID = Convert.ToInt32
(customerDropDown.SelectedValue);
int productID = Convert.ToInt32
(productDropDown.SelectedValue);
Working with multiple tables
Page 6
int orderMonth = odate.Month;
int orderDay = odate.Day;
int orderYear = odate.Year;
string query = "INSERT INTO SalesTransaction (OrderID, " +
"CustomerID, ProductID, Quantity, OrderDate) VALUES " +
"(@OrderValue, @CustomerValue, @ProductValue, " +
"@OrderQty, @DateValue)";
System.Data.OleDb.OleDbCommand ocmd =
new System.Data.OleDb.OleDbCommand(query, new
System.Data.OleDb.OleDbConnection(CSTR));
ocmd.Parameters.AddWithValue("@OrderValue", orderID);
ocmd.Parameters.AddWithValue("@CustomerValue", customerID);
ocmd.Parameters.AddWithValue("@ProductValue", productID);
ocmd.Parameters.AddWithValue("@OrderQty", orderQuantity);
ocmd.Parameters.AddWithValue("@DateValue", orderMonth +
"/" + orderDay + "/" + orderYear);
ocmd.Connection.Open();
ocmd.ExecuteNonQuery();
ocmd.Connection.Close();
int newInventoryLevel = invQuantity - orderQuantity;
query = "UPDATE Product SET Inventory = @NewInvValue WHERE
ProductID = @ProdValue";
ocmd =
new System.Data.OleDb.OleDbCommand(query, new
System.Data.OleDb.OleDbConnection(CSTR));
ocmd.Parameters.AddWithValue("@NewInvValue",
newInventoryLevel.ToString());
ocmd.Parameters.AddWithValue("@ProdValue",
productID.ToString());
ocmd.Connection.Open();
ocmd.ExecuteNonQuery();
ocmd.Connection.Close();
orderBox.Text = "";
customerDropDown.SelectedIndex = 0;
productDropDown.SelectedIndex = 0;
qtyBox.Text = "";
}
}
}
The Page_Load() method
The only thing happening in this method is the population of dateLabel. We do that
here because this ensures that the code will be executed (the Page_Load() event
occurs every time the ASPX page is (re)loaded). Notice also that we don’t use a
TextBox for the order date – we use a Label. This is because the user should not be
Working with multiple tables
Page 7
allowed to change the date of an order – it should simply reflect when the order was
placed.
The generateID_Click() method
Remember from the schema that OrderID is the primary key for the SalesTransaction
table. Therefore, its value must be unique. If the user does not know which OrderIDs
have already been used, he can use the generateID Button to create a unique order
number. The algorithm for doing this is simple – it’s just the highest value for OrderID in
the database “plus one.” So if the highest OrderID value is 301, the new OrderID will be
302.
It turns out it’s pretty easy to do this, thanks to the SELECT MAX function in SQL. The
query we will use is:
SELECT MAX(OrderID) FROM SalesTransaction
Once the query and the OleDbCommand object have been set up, we open a connection
to the the database (ocmd.Connection.Open()), execute the query
(ocmd.ExecuteReader()), and read the single line in the result set (dr.Read()).
We know there will only be one record returned because there can be only one “highest”
value.
Furthermore, since we’re only looking at one field (OrderID), that returned record will
only have one field of data. We retreive that value with this statement:
int highestID = dr.GetInt32(0);
Now (after we close the connection using ocmd.Connection.Close()) we add 1 to
that value and put it in the orderBox field:
int newID = highestID + 1;
orderBox.Text = newID.ToString();
Isn’t this method kind of stupid?
We should acknowledge that this isn’t a very “smart” method. For example, say there are
only two orders in the database – one has OrderID 1 and the other has OrderID 500. Our
application will skip 2 through 499 and instead make 501 the next OrderID. We could
employ more complex logic to make sure that every number was used, but this will work
fine for now.
Working with multiple tables
Page 8
The getInventoryLevel() method
This method retrieves the inventory level (number of products in stock) of the product
currently selected in the productDropDown. We’ll use this method in two places – the
productDropDown_SelectedIndexChanged() method and the submitButton_Click()
method. The inventory level is stored in the Inventory field of the Products table.
Therefore, this method builds and executes the following query:
SELECT Inventory FROM Product WHERE ProductID = selectedvalue
Where selectedvalue is the “value” of the selected item in productDropDown, which
we retrieve using productDropDown.SelectedValue.
This is accomplished through the following parameterized query:
string query = "SELECT Inventory FROM Product WHERE " +
"ProductID=@ProdValue";
And the @ProdValue parameter is added with the following line:
ocmd.Parameters.AddWithValue("@ProdValue",
productDropDown.SelectedValue);
Like the SELECT MAX query we used in the generateID_Click() method, we
know there will only be one record returned from this query because the productID is
unique. Furthermore, this single record will only have one field – Inventory. We
retrieve that value with the statement:
int quantity = dr.GetInt32(0);
Finally, we close the connection and return the value quantity from the method.
ocmd.Connection.Close();
return quantity;
The productDropDown_SelectedIndexChanged() method
Recall that when the user selected a product from the productDropDown, we want the
application to automatically update qtyLabel. We do this so the user knows the
maximum number of the product he can order. Now that we’ve already created the
getInventory() method, updating the qtyLabel is easy. First we want to get the
inventory level for the product selected in the productDropDown:
int invQuantity = this.getInventoryLevel();
Working with multiple tables
Page 9
Now all we have to do is convert that value to a string a place it in the Text attribute of
the qtyLabel:
qtyLabel.Text = invQuantity.ToString();
Actually, this code by itself won’t do anything. This method only executes once the page
refreshes by posting to itself – this is why we turned on the AutoPostBack attribute of
the productDropDown. When it does “post back” to itself, C# looks for the method
associated with the event that caused the postback and executes its code.
The submitButton_Click() method
Finally, we’ll look at the submitButton_Click() method. This is the method that
actually builds and executes the queries. What’s important here is that there are two
queries, not one. The first is an INSERT query that adds a record to the
SalesTransaction table with the order information. The second is an UPDATE
query that updates the Product table with the new inventory level.
The first thing the method does is check whether the user has ordered more units than are
in inventory. Remember that the getInventoryLevel() method retrieves the value
in the Inventory field for the record that corresponds to the selected item in the
productDropDown. This compared to the value (converted to an integer) in the
TextBox qtyBox:
int invQuantity = this.getInventoryLevel();
int orderQuantity = Convert.ToInt32(qtyBox.Text);
if (orderQuantity > invQuantity)
{
statusLabel.Text = "Order quantity exceeds inventory!";
}
If orderQuantity does not exceed invQuantity, then the application knows its
okay to build and execute the queries. To build the query to insert the new record into the
SalesTransaction table, we need to retrieve the data about the order from the form:
int
int
int
int
int
int
orderID = Convert.ToInt32(orderBox.Text);
customerID = Convert.ToInt32(customerDropDown.SelectedValue);
productID = Convert.ToInt32(productDropDown.SelectedValue);
orderMonth = odate.Month;
orderDay = odate.Day;
orderYear = odate.Year;
Now we use this information to build the parameterized query:
Working with multiple tables
Page 10
string query = "INSERT INTO SalesTransaction (OrderID, " +
"CustomerID, ProductID, Quantity, OrderDate)" +
"VALUES (@OrderValue, @CustomerValue, " +
"@ProductValue, OrderQty, @DateValue)";
Once the query is built, we just build the OleDbCommand object and use the following
statements to set up the parameters:
System.Data.OleDb.OleDbCommand ocmd =
new System.Data.OleDb.OleDbCommand(query,
new System.Data.OleDb.OleDbConnection(CSTR));
ocmd.Parameters.AddWithValue("@OrderValue", orderID);
ocmd.Parameters.AddWithValue("@CustomerValue", customerID);
ocmd.Parameters.AddWithValue("@ProductValue", productID);
ocmd.Parameters.AddWithValue("@OrderQty", orderQuantity);
ocmd.Parameters.AddWithValue("@DateValue", orderMonth + "/" +
orderDay + "/" + orderYear);
Now we open the database, execute the query, and close the database:
ocmd.Connection.Open();
ocmd.ExecuteNonQuery();
ocmd.Connection.Close();
The second query to build is the UPDATE query to the Product table. There are two
pieces of information that we need for the query. The first is the “new” inventory level,
which is the number of items ordered (orderQuantity) subtracted from the current
inventory level (invQuantity). The second piece of information is the productID, which
we can just reuse from the previous INSERT query.
int newInventoryLevel = invQuantity - orderQuantity;
query = "UPDATE Product SET Inventory = @NewInvValue WHERE " +
"ProductID = @ProdValue";
Now, once again, we need to set up the parameter values and execute the query:
ocmd =
new System.Data.OleDb.OleDbCommand(query,
new System.Data.OleDb.OleDbConnection(CSTR));
ocmd.Parameters.AddWithValue("@NewInvValue",
newInventoryLevel.ToString());
ocmd.Parameters.AddWithValue("@ProdValue", productID.ToString());
ocmd.Connection.Open();
ocmd.ExecuteNonQuery();
ocmd.Connection.Close();
Working with multiple tables
Page 11
Finally, we clear the form for the next order:
orderBox.Text = "";
customerDropDown.SelectedIndex = 0;
productDropDown.SelectedIndex = 0;
qtyBox.Text = "";
Problem 1
If you set up the form exactly according to the graphic on page 3, you’ll notice that we
never created the target page of the “See orders/inventory” hyperlink
(allOrders.aspx). Complete the new page so that it looks like this:
This page consists of two GridViews. The first is linked to the SalesTransaction
table. The second is linked to the Product table. Also, don’t forget the “Place new
order” hyperlink, which returns the user to the order form.
Working with multiple tables
Page 12
Problem 2
Modifiy the application so that the customerDropDown displays LastName,
FirstName as the display data field instead of just LastName. For example, the two
“Simpson” entries would display in the DropDownList as “Simpson, Homer” and
“Simpson, Marge.”
To do this, you’ll need to create the code that populates the customerDropDown
(instead of relying on the binding “wizard” in the current applciation). As a guide, look at
the the populateDropDowns() method in “Coding ADO.NET Objects.” The query
you use will SELECT the CustomerID, FirstName, and LastName fields FROM
the Customer table.
Then you can retrieve the two name fields from the OleDbDataReader and create a
string that concatenates them. This concatentated string becomes the first parameter of
the new ListItem you’ll add to the customerDropDown, and the CustomerID
(converted to a string) becomes the ListItem’s second parameter.