Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 69 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
69
Dung lượng
584,08 KB
Nội dung
Chapter 7 32 CREATE PROCEDURE dbo.spRetrieveCategories AS select * from categories RETURN 9. Next, add this procedure to the clsDatabase class. This procedure gets called from the LoadCompleteDataSet function created previously and outputs the DataSet information to the Output window. Sub WriteCompleteDataSetToOutputWindow(ByVal dsData As DataSet) '**************************************************************** 'Write data to the output window from the DataSet '**************************************************************** Try Dim oRow As DataRow Dim strRecord As String 'write some data in the Products table to the Output window 'to show that the data is there. For Each oRow In dsData.Tables("Products").Rows strRecord = "Product Id: " & oRow("ProductId").ToString() strRecord = strRecord & " Product Name: " strRecord = strRecord & oRow("ProductName").ToString() strRecord = strRecord & " Supplier Id: " strRecord = strRecord & oRow("SupplierId").ToString() Console.WriteLine(strRecord) Next 'write some data in the Suppliers table to the Output window 'to show that the data is there. For Each oRow In dsData.Tables("Suppliers").Rows strRecord = "Supplier Id: " & oRow("SupplierId").ToString() strRecord = strRecord & " Company Name: " strRecord = strRecord & oRow("CompanyName").ToString() strRecord = strRecord & " Contact Name: " strRecord = strRecord & oRow("ContactName").ToString() Console.WriteLine(strRecord) Next 'write some data in the Categories table to the Output window 'to show that the data is there. For Each oRow In dsData.Tables("Categories").Rows strRecord = "Category Id: " & oRow("CategoryId").ToString() strRecord = strRecord & " Category Name: " strRecord = strRecord & oRow("CategoryName").ToString() strRecord = strRecord & " Description: " strRecord = strRecord & oRow("Description").ToString() Console.WriteLine(strRecord) Next Reading Data into the DataSet 33 Catch 'error handling goes here UnhandledExceptionHandler() End Try End Sub 10.Finally, add the UnhandledExceptionHandler to the clsDatabase class Sub UnhandledExceptionHandler() 'display an error to the user MsgBox("An error occurred. Error Number: " & Err.Number & _ " Description: " & Err.Description & " Source: " & Err.Source) End Sub How It Works In this section, first we created a new class, clsDatabase.vb, and added the following namespaces: Imports System.Data Imports System.Data.SqlClient The DataSet features we will be using come from these two namespaces. If you do not have these references in your class module, then some of the code that follows will generate a compiler error when you try to build your project as they are required to locate certain classes and methods. The first namespace (System.Data) is for general data access, and the second namespace (System.Data.SqlClient) is SQL Server specific. Next we added a generic routine that populates a DataSet with the results of a stored procedure or SQL statement. Dim sqlConn As New SqlClient.SqlConnection(strConnection) sqlConn.Open() Dim adapterProducts As New SqlClient.SqlDataAdapter() adapterProducts.TableMappings.Add("Table", strTableName) Dim cmdTable As SqlClient.SqlCommand = New _ SqlClient.SqlCommand(strSQLorStoredProc, _ sqlConn) 'run stored procedure or SQL statement accordingly If blnStoredProcedure Then cmdTable.CommandType = CommandType.StoredProcedure Else cmdTable.CommandType = CommandType.Text End If Chapter 7 34 adapterProducts.SelectCommand = cmdTable 'fill the data set with the table information as specified in 'the stored procedure or from the results of the SQL statement adapterProducts.Fill(dsDataSet) In the code snippet from the PopulateDataSetTable function above, notice how a SqlConnection is declared first, and then opened. Then, a new SqlDataAdapter is declared. SqlDataAdapter is the class used to fill and update DataSets. Note that OleDbDataAdapter can also be used, and it works with OLE DB data sources, including SQL Server. SqlDataAdapter on the other hand only works with SQL Server databases but, in such cases, it outperforms OleDbDataAdapter. Next, table mappings are defined for the adapter. The primary purpose of a table mapping is to specify what the table in the DataSet should be called, regardless of the source it is coming from. The first parameter to the Add method is the source table and the second is the destination table. The source table is the table in the data source to retrieve information from while the destination table is the table in the DataSet that the data goes into. When populating the DataSet from a stored procedure or SQL statement, simply specifying the default value of "Table" for the source table is sufficient. Dim adapterProducts As New SqlClient.SqlDataAdapter() adapterProducts.TableMappings.Add("Table", strTableName) Dim cmdTable As SqlClient.SqlCommand = New _ SqlClient.SqlCommand(strSQLorStoredProc, _ sqlConn) A Command object is declared next to define the SQL statement or stored procedure to base the DataSet table on, as well as which database connection to use. The Command object is then associated with the adapter, which is how the adapter is made aware of from where to retrieve the results. 'run stored procedure or SQL statement accordingly If blnStoredProcedure Then cmdTable.CommandType = CommandType.StoredProcedure Else cmdTable.CommandType = CommandType.Text End If adapterProducts.SelectCommand = cmdTable Finally, using the DataAdapter, the DataSet can be populated from the SQL statement or stored procedure. 'fill the data set with the table information as specified in 'the stored procedure or from the results of the SQL statement adapterProducts.Fill(dsDataSet) sqlConn.Close() Reading Data into the DataSet 35 After creating the generic function to populate a DataSet, we then created a function called PopulateDataSetRelationship to relate two tables in a DataSet together. Recall that a DataSet is an in-memory copy of information. It can contain tables that are totally independent from the source, once placed in memory. Thus, even though relationships may exist in a database, when you populate such information into a DataSet, those relationships do not carry over between tables. You can create relationships between tables in your DataSet so that tables in the in-memory copy relate to each other. This example makes use of the DataRelation and DataColumn objects. After the DataColumns to be related are specified (as columns already present in the DataSet), then the DataRelation object creates the relationship. Dim drRelation As DataRelation Dim dcCol1 As DataColumn Dim dcCol2 As DataColumn dcCol1 = _ dsDataSet.Tables(strTable1).Columns(strColumnFromTable1) dcCol2 = _ dsDataSet.Tables(strTable2).Columns(strColumnFromTable2) drRelation = New System.Data.DataRelation _ (strRelationshipName, dcCol1, dcCol2) dsDataSet.Relations.Add(drRelation) In the above code, dcCol1 is the first table in the DataRelation method's parameters, and dcCol2 is the second. This means that dcCol1 is the parent table, and dcCol2 is the child table. A table is known as the parent table because it is the one that ensures the uniqueness of the key field on which this relationship hinges. If you were to reverse the order of these parameters, then you would likely get a run-time error about non-unique columns. Now that we have our generic functions in place to populate a DataSet from a stored procedure or SQL statement, and one to create relationships in a DataSet, we're ready to populate a DataSet with information from the Products, Suppliers, and Categories tables. We created a LoadCompleteDataSet function to populate the DataSet that will be used in the application to store some values to populate the ComboBoxes. We sometimes refer to these as code tables. Notice how we get to make use of the generic functions we created before to populate the DataSet. We populate the Products, Suppliers, and Categories tables in the DataSet by calling the PopulateDataSetTable function and passing the proper parameters, one of them being the stored procedure to run to retrieve the records. 'Create a Products table in the DataSet dsData = PopulateDataSetTable(strConnection, "Products", _ "spRetrieveProducts", blnRunStoredProc, dsData) 'Create a Suppliers table in the DataSet dsData = PopulateDataSetTable(strConnection, "Suppliers", _ "spRetrieveSuppliers", blnRunStoredProc, dsData) 'Create a Categories table in the DataSet dsData = PopulateDataSetTable(strConnection, "Categories", _ "spRetrieveCategories", blnRunStoredProc, dsData) 'Create the relationship between Products and Suppliers tables Chapter 7 36 dsData = PopulateDataSetRelationship("Suppliers", "Products", _ "SupplierId", "SupplierId", "ProductsVsSuppliers", _ dsData) 'Create the relationship between Products and Categories tables dsData = PopulateDataSetRelationship("Categories", "Products", _ "CategoryId", "CategoryId", "ProductsVsCategories", _ dsData) WriteCompleteDataSetToOutputWindow(dsData) Stored procedures should be used to retrieve data whenever possible because they are pre-compiled on the database server and contain an execution plan which tells SQL Server how to execute them. This means that they execute faster than a SQL statement being passed on the fly to the database. Thus, retrieving values to populate our first DataSet was handled using stored procedures instead of a SQL statement in Visual Basic .NET code. Later, we will look at an example of when you might need to use a SQL statement in the code instead of a stored procedure. Such cases occur typically when it would be extremely difficult, if not impossible, to determine the SQL statement up front such that it could be stored in a stored procedure. In instances like that, it makes sense to just create the SQL statement in the Visual Basic .NET code and pass the SQL statement to the database. After populating the DataSet, we then created the relationships between the tables. Near the end of the PopulateDataSetTable function is a call to the WriteCompleteDataSetToOutputWindow procedure. We can comment the call to this out later but, in this chapter, we keep it in to verify that the DataSet is being correctly populated with the results of the query. Let's have a quick look at what this procedure accomplishes: Dim oRow As DataRow Dim strRecord As String 'write some data in the Products table to the Output window 'to show that the data is there. For Each oRow In dsData.Tables("Products").Rows strRecord = "Product Id: " & oRow("ProductId").ToString() strRecord = strRecord & " Product Name: " strRecord = strRecord & oRow("ProductName").ToString() strRecord = strRecord & " Supplier Id: " strRecord = strRecord & oRow("SupplierId").ToString() Console.WriteLine(strRecord) Next In this case, we used the DataRow object to manipulate the DataSet and output all rows but only certain columns to the Output window. Lastly, we added the UnhandledExceptionHandler to the clsDatabase class. This procedure will handle all unhandled exceptions that get raised in the clsDatabase class. This can be modified to handle errors in the clsDatabase class in whatever manner you desire. Reading Data into the DataSet 37 It is very important that you understand what we just did in this section. We populated a DataSet with all of the records in the Products, Suppliers, and Categories tables and then related them together. As you know, a DataSet is an in-memory copy of data. This means that it consumes memory based on the amount of records in your DataSet. The procedures we created in this section can be used in instances where your recordset is small, but you would never want to populate a DataSet with thousands of records. We just used this for illustration purposes to show you the concept of a DataSet and relationships between tables in the DataSet. In practice, you have to make good judgment calls based on the number of records being returned to determine whether this is really a good idea or not. Now, let's move on to creating the code that will populate a DataSet from a SQL Statement and then on to writing the code to bring everything together so that it executes when the user specifies search criteria and clicks the Search button. Populating a DataSet From a SQL Statement Now we are ready to create a generic function that will populate a DataSet by executing a SQL statement that is passed in. We will then call this function later to have it execute the SQL statement that gets generated by the search criteria specified by the user. Try It Out – Populating a DataSet from a Dynamic SQL Statement 1. This code below should be placed under the code for the clsDatabase.vb class: Function LoadSearchDataSet(ByVal strConnection As String, ByVal strSQL _ As String) As DataSet '**************************************************************** 'The purpose of this function is to create and populate a data 'set based on a SQL statement passed in to the function. '**************************************************************** Try Dim dsData As New DataSet() 'call the table in the local dataset "results" since the values 'may be coming from multiple tables. Dim strTableName As String = "Results" Dim blnRunStoredProc As Boolean = False dsData = PopulateDataSetTable(strConnection, strTableName, _ strSQL, blnRunStoredProc, dsData) WriteSampleDataToOutputWindow(dsData) 'return the data set to the calling procedure Return dsData Catch 'error handling goes here UnhandledExceptionHandler() Chapter 7 38 End Try End Function 2. This code should also be placed under the code for the clsDatabase.vb class: Sub WriteSampleDataToOutputWindow(ByVal dsdata As DataSet) '**************************************************************** 'Write data to the output window from the DataSet '**************************************************************** Try Dim oRow As DataRow Dim oColumn As DataColumn Dim strRecord As String 'write some data in the to the Output window 'to show that the data is there and that the SQL statement 'worked. For Each oRow In dsdata.Tables("Results").Rows strRecord = oRow(0).ToString() strRecord = strRecord & " " & oRow(1).ToString() strRecord = strRecord & " " & oRow(2).ToString() strRecord = strRecord & " " & oRow(3).ToString() strRecord = strRecord & " " & oRow(4).ToString() Console.WriteLine(strRecord) Next Catch 'error handling goes here UnhandledExceptionHandler() End Try End Sub How It Works The LoadSearchDataSet function calls the PopulateDataSetTable function with the parameter specifying that it is not a stored procedure but, rather, a SQL statement that the DataSet will be based upon. Dim dsData As New DataSet() 'call the table in the local dataset "results" since the values 'may be coming from multiple tables. Dim strTableName As String = "Results" Reading Data into the DataSet 39 Dim blnRunStoredProc As Boolean = False dsData = PopulateDataSetTable(strConnection, strTableName, _ strSQL, blnRunStoredProc, dsData) After creating the DataSet, a call is made to the WriteSampleDataToOutputWindow procedure. WriteSampleDataToOutputWindow(dsData) This procedure is called to write some sample data to the Output window to verify that the search results were populated correctly, based on the criteria specified by the user. Dim oRow As DataRow Dim oColumn As DataColumn Dim strRecord As String 'write some data in the to the Output window 'to show that the data is there and that the SQL statement 'worked. For Each oRow In dsdata.Tables("Results").Rows strRecord = oRow(0).ToString() strRecord = strRecord & " " & oRow(1).ToString() strRecord = strRecord & " " & oRow(2).ToString() strRecord = strRecord & " " & oRow(3).ToString() strRecord = strRecord & " " & oRow(4).ToString() Console.WriteLine(strRecord) Next This procedure call can be commented out later but, for now, we want to see that our search results are coming back correctly. We are now ready to write the code to generate the dynamic SQL statement based on the criteria specified by the user. In the process, we will modify the search forms so they call all of the code we created in the past two sections to populate the DataSets. Building the SQL Statement Based on User Input In this section, we will write the code to generate a SQL statement dynamically based on the criteria specified by the user on either of the search forms. Try It Out – Creating a Dynamic SQL Statement Based on User Input 1. Add the following function to clsDatabase.vb: Function PadQuotes(ByVal strIn As String) As String '********************************************************************* 'The purpose of this (very short but important) function is to search for 'the occurrence of single quotes within a string and to replace any Chapter 7 40 'single quotes with two singles quotes in a row, so that, when executing 'the SQL statement, an error will not occur due to the database thinking 'it has reached the end of the field value. In SQL Server and some other 'databases, if you put such a delimiter twice in a row when passing a 'string SQL statement for it to execute (versus a stored procedure where 'this doesn't apply), it knows that you want to use it once - versus that 'it symbolizes the end of the value. Example: Grandma's Boysenberry then 'becomes Grandma''s Boysenberry as the database expects. '********************************************************************* Try PadQuotes = strIn.Replace("'", "''") Catch 'error handling goes here UnhandledExceptionHandler() End Try End Function 2. Next, add this code to the clsDatabase.vb class. This code will build the WHERE clause of the SQL statement for our search screens. Function BuildSQLWhereClause(ByVal strTableName As String, ByVal _ strQueryOperator As String, ByVal strSearchValue As String, _ ByVal blnPriorWhereClause As Boolean, ByVal strWhereClause As _ String, ByVal blnNumberField As Boolean) As String '********************************************************************** 'The purpose of this function is to add the parameters passed in to 'the WHERE clause of the SQL Statement. '********************************************************************** Try Dim strWhere As String = strWhereClause Dim strDelimiter1 As String Dim strDelimiter2 As String If blnPriorWhereClause = False Then strWhere = " WHERE " Else strWhere = strWhere & " AND " End If Select Case strQueryOperator Case "Equals" If blnNumberField Then strDelimiter1 = " = " strDelimiter2 = "" Else strDelimiter1 = " = '" Reading Data into the DataSet 41 strDelimiter2 = "' " End If Case "Starts With" strDelimiter1 = " LIKE '" strDelimiter2 = "%' " Case "Ends With" strDelimiter1 = " LIKE '%" strDelimiter2 = "' " Case "Contains" strDelimiter1 = " LIKE '%" strDelimiter2 = "%'" Case "Greater Than" strDelimiter1 = " > " strDelimiter2 = "" Case "Less Than" strDelimiter1 = " < " strDelimiter2 = "" End Select 'Add the new criteria to the WHERE clause of the SQL Statement. 'Note that the PadQuotes function is also being called to make 'sure that if the user has a single quote in their search value, 'it will put an additional quote so the database doesn't 'generate an error. strWhere = strWhere & strTableName & strDelimiter1 & _ PadQuotes(strSearchValue) & strDelimiter2 Return strWhere Catch 'error handling goes here UnhandledExceptionHandler() End Try End Function 3. Next, add this function to clsDatabase.vb that will be used to build the SELECT and FROM Clause of the dynamic SQL statement: Function BuildSQLSelectFromClause(ByVal strSearchMethod As String) _ As String '********************************************************************** 'The purpose of this function is to create the SELECT FROM clause for 'the SQL statement depending on whether the search is for Products [...]... important topic that can often be overlooked in database programming: the problem of the single quote character in strings This character needs special treatment, and a poorly designed application will fail if the user attempts to use a string containing a single quote (apostrophe) for a database search query This is because SQL uses single quotes to denote the beginning and end of a query string (that is,... clsdatabase As New clsDatabase() Dim strSQL As String = "" 'Load a data set with the complete Products, Suppliers, and 'categories tables (to be used later as code tables to display 'choices in a list, etc.) dsData = clsdatabase.LoadCompleteDataSet(CONN) 'Load a data set with the search results based on the criteria 'specified by the user on the form strSQL = BuildSQLStatement() dsResults = clsdatabase.LoadSearchDataSet(CONN,... object-oriented concepts new in Visual Basic NET Let's take a quick look at an example so that you can see visually how this works Suppose you have the Products Search Utility form open and you specify the following criteria - Product Name Contains the word berry: After clicking the Search button on the form, you should then see some results in the Output window similar to those shown below: 52 Reading Data into... itself? 5 What is the SQL statement that would be assembled when the user asks to see all seafood products under $10? 6 Why do we need to take special care when handling quotes within user input? Does this apply with stored procedures too? 7 What is the difference between SqlDataAdapter and OleDbDataAdapter? Answers are available at http://p2p.wrox.com/exercises/ 54 Reading Data into the DataSet 55 Chapter... similar pattern In the code below, the first line declares a new instance of the clsDatabase to allow us to invoke the methods used to build our SQL clauses Then, the second line calls the BuildSQLSelectFromClause to build the SelectFrom part of the SQL statement 50 Reading Data into the DataSet Dim clsDb As New clsDatabase() strSelectFromCriteria = _ clsDb.BuildSQLSelectFromClause("Products") Finally,... to the clsDb.BuildSQLWhereClause method Since we already have clsDb declared in this procedure as a new instance of clsDatabase, all we have to do – to pass a pointer to its BuildSQLWhereClause method as a parameter – is to place an AddressOf statement before clsDb This tells Visual Basic NET to pass a pointer to the location in memory where that method resides, so that the Delegate function can then... CheckSearchCriteria(cbocriteria5.Text, txtcriteria5.Text, _ "Region", strWhereCriteria, blnPriorWhere, _ "false", AddressOf clsDb.BuildSQLWhereClause) CheckSearchCriteria(cbocriteria6.Text, txtcriteria6.Text, _ "PostalCode", strWhereCriteria, blnPriorWhere, _ "false", AddressOf clsDb.BuildSQLWhereClause) 'put the SELECT, FROM, and WHERE clauses together into one 'string 45 Reading Data into the DataSet... this Click event code in the BaseSearchForm, then the clsDatabase class would have needed to be present in that project as well, since we are creating an instance of it in the code Or, alternatively, the Click event could have been added to the base and then the clsDatabase class referenced from another project in which it resides Since the clsDatabase resides in the MainApp, it didn't make sense to... strings into this "two quotes in a row" format Below is the single line of code that we used to create the PadQuotes function in clsDatabase: PadQuotes = strIn.Replace("'", "''") You only need to use the PadQuotes function when creating and executing SQL statements from Visual Basic NET If you are passing parameters to stored procedures, as we will see in a later chapter, you do not need to pad the quotes,... strWhereCriteria = clsDatabase.BuildSQLWhereClause _ (strFieldName, strMatchCriteria, strFilterCriteria, _ blnPriorWhere, strWhereCriteria, blnNumberField) Instead, we decided to use delegation so that the clsDatabase.BuildSQLWhereClause could be passed into the CheckSearchCriteria procedure as a parameter This is useful in our scenario because we don't have a reference to the clsDatabase class in the . instances like that, it makes sense to just create the SQL statement in the Visual Basic .NET code and pass the SQL statement to the database. After populating the DataSet, we then created the relationships. in clsDatabase: PadQuotes = strIn.Replace("'", "''") You only need to use the PadQuotes function when creating and executing SQL statements from Visual Basic .NET. . instance of clsDatabase, all we have to do – to pass a pointer to its BuildSQLWhereClause method as a parameter – is to place an AddressOf statement before clsDb. This tells Visual Basic .NET to pass