Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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
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.