Professional ASP.NET 1.1 [Electronic resources]

Alex Homeret

نسخه متنی -صفحه : 244/ 69
نمايش فراداده

Common Data Access Tasks with .NET

To demonstrate the basics of working with relational data in .NET, we've put together a series of sample pages that show the various objects in action. Figure 8-7 shows the

default main menu page for the samples:

Figure 8-7:

You can download the samples to run on your own server at http://www.daveandal.net/books/8900/. You can also run many of them online at the same URL. The samples are available in both VB and C#, and you can choose which to install – or install both sets.

The examples for this chapter are in the Introduction to Relational Data Access in .NET section, and this link displays the

default page for these sample pages, as shown in Figure 8-8.

Figure 8-8:

The first three groups of links show the three basic techniques for accessing relational data. Each group demonstrates four different connection types: an OLEDB provider for SQL Server, a direct SQL Server TDS connection, a connection through the .NET ODBC driver, and a connection to an Access database file through the Jet provider for Microsoft Access. There is also an example of using a user control that returns a

DataSet . We'll be examining all these sample pages.

Setting Up the Samples on Your System

The downloadable sample files contain a

WroxBooks.mdb Access database, which you can use with the Jet examples, and a set of SQL scripts that you can use to create the sample

WroxBooks database on your own local SQL Server. Instructions for using the scripts are in the

readme.txt file located within the

database folder of the samples.

You'll also need to edit the connection strings in the

web.config file that is installed in the root folder of the examples to suit your setup. The

<appSettings> section of the

web.config file contains declarations of the connection strings for all of the examples for this book, but the ones that are relevant to this chapter are highlighted in the following code. Notice that there are four, one for each of the providers/drivers used in the example pages:

<configuration>
... other settings here ...
<appSettings>

<add key="DsnWroxBooksSql"

value="server=delmonte; database=WroxBooks; user id=sa; password=" />

<add key="DsnWroxBooksOleDb"

value="provider=SQLOLEDB.1; data source=delmonte;

initial catalog=WroxBooks; uid=sa; pw=" />

<add key="DsnWroxBooksJet"

value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" />

<add key="DsnWroxBooksOdbc"

value="DRIVER={SQL Server}; SERVER=delmonte;

DATABASE=WroxBooks; uid=sa; pw=;" /> ... other settings here ... </appSettings> </configuration>

Any page within the samples can access and use these connection strings by using:

strSQLConnect = ConfigurationSettings.AppSettings("DsnWroxBooksSql")

strOLEDBConnect = ConfigurationSettings.AppSettings("DsnWroxBooksOleDb")

strJetConnect = ConfigurationSettings.AppSettings("DsnWroxBooksJet")

strOdbcConnect = ConfigurationSettings.AppSettings("DsnWroxBooksOdbc")

Setting Up the Required File Access Permissions

Some of the examples files require write access to the server's

wwwroot folder and subfolders below this. By default in Windows NT, Windows 2000, and Windows XP, ASP.NET runs under the context of the ASPNET account that is created by the installation and setup of the .NET Framework. This is a relatively unprivileged account that has similar permissions by default as the IUSR_machinename account that is used by Internet Information Services.

To give folders on your test server write access for ASP.NET, right-click on the

wwwroot folder in Windows Explorer and open the Properties dialog. In the Security tab, select the ASPNET account and give it Write permission or Full Control. Then click Advanced and tick the checkbox at the bottom of this page (Reset permissions on all child objects…).

Alternatively, configure ASP.NET to run under the context of the local System account by editing the

machine.config file located in the

config directory of the installation root. By default, this is the

C:\WINNT\Microsoft.NET\Framework\[version]\CONFIG\ directory. Change just the

userName attribute in the

<processModel> element within the

<system.web> section of this file to:

<processModel userName="system" password="autogenerate" ... />

Important

Do this only while experimenting and only on a development server. For production servers, set up only the minimal permissions required for your applications to run.

ASP.NET with IIS 6.0 and Windows Server 2003

While all the this is true for IIS 4.0 and IIS 5.0, as installed with Windows NT, Windows 2000, and Windows XP, the new version of IIS supplied with Windows Server 2003 (IIS 6.0) works in a slightly different way. Security and account permissions are discussed in Chapter 14. However, to enable the example pages to run on Windows Server 2003 you only need to know the basics here.

By default, in Windows Server 2003, web sites run within Application Pools and the worker processes used for accessing resources run under the context of an account named NETWORK SERVICE. Windows Server 2003 creates an account group called IIS_WPG, of which the IWAM_machinename, LOCAL SERVICE, NETWORK SERVICE and SYSTEM accounts are automatically members.

It means that you can use this group to configure access to resources for ASP.NET running under the default IIS 6.0 configuration. Alternatively, you can just assign the necessary Write permission directly to the NETWORK SERVICE account if you prefer more fine-grained control.

You can also configure IIS 6.0 to run in a special compatibility mode called IIS 5.0 Isolation Mode (in the Service tab of the Properties dialog for the Web Sites entry in the IIS Manager). In this case, IIS 6.0 runs ASP.NET just like it does under IIS 5.0, and the accounts used and permission settings you make are the same as in IIS 5.0.

So, the ASPNET account is used for ASP.NET resources, and the IWAM_machinename account is used for other resources. And an account named IWAM_machinename is used for out-of-process execution of components in this mode, just as in IIS 5.0.

Note

For more information of the IIS and ASP.NET security model in Windows Server 2003, open the Help file from IIS Manager and navigate to the Server Administration Guide | Security | Access Control | Web Site Permissions section.

Using a DataReader Object

The first group of links in the relational data access menu shows the

DataReader in action. This is the nearest equivalent to the

Connection /

Recordset data access technique used in traditional ADO. Figure 8-9 shows the result of running the OLEDB example. The others from the same group (the SQL TDS, ODBC and Jet provider examples) provide identical output, but with different connection strings.

Figure 8-9:

The code in the page (

datareader-oledb.aspx ) is placed within the

Page_Load event handler. So, it runs when the page loads. The code inserts the connection string, SQL

SELECT statement, and the results into

<div> elements within page. All the code is fully commented, and we've included elementary error handling to display any errors. However, only the relevant data access code has been shown here. You can examine the entire source code for any of the pages by clicking the [view source] link at the bottom.

The DataReader Example Code

The following code has been used in this example:

'get connection string from web.config file and display it

strConnect = ConfigurationSettings.AppSettings("DsnWroxBooksOleDb")

outConnect.InnerText = strConnect

'specify the SELECT statement to extract the data and display it

strSelect = "SELECT * FROM BookList WHERE ISBN LIKE '07645437%'"

outSelect.InnerText = strSelect

'create a new Connection object using the connection string

Dim objConnect As New OleDbConnection(strConnect)

'open the connection to the database

objConnect.Open()

'create a new Command using the connection object and select statement

Dim objCommand As New OleDbCommand(strSelect, objConnect)

'declare a variable to hold a DataReader object

Dim objDataReader As OleDbDataReader

'execute the SQL statement against the command to fill the DataReader

objDataReader = objCommand.ExecuteReader()

The first step is to get the connection string from the

web.config file, and then specify the SQL statement. These are displayed as the code runs in

<div> elements named

outConnect and

outSelect (located within the HTML of the page). Then, we create a new instance of an

OleDbConnection object, specifying the connection string as the single parameter of its constructor.

After opening the connection by calling the

Open method, you need an

OleDbCommand object. This will be used to execute the statement and return a new

OleDbDataReader object through which you can access the results of the query. The SQL statement and the active

Connection object are specified as the parameters to the

OleDbCommand object constructor: You can then call the

ExecuteReader method of the

OleDbCommand object. This returns an

OleDbDataReader object that is connected to the result rowset.

Displaying the Results

A

DataReader allows you to iterate through the results of a SQL query, much like you do with a traditional ADO

Recordset object. However, unlike in the ADO

Recordset , in a

DataReader you must call the

Read method first to be able to access the first row of the results. Afterwards, just call the

Read method repeatedly to get the next row of the results until it returns

False (which indicates that the end of the results set has been reached).

Note

We no longer have a

MoveNext method. Forgetting to include this statement was found by testers to be the most common reason for problems when working with the

Recordset object in ADO.

As was common practice in ASP 3.0 and earlier, you can build up an HTML

<table> to display the data. However, as you're working with ASP.NET now, this example actually creates the definition of the table as a string and then inserts it into a

<div> element elsewhere in the page (rather than the ASP-style technique of using

Response.Write directly). The following code was used to create the output shown in Figure 8-9:

Dim strResult As String = "<table>"

'iterate through the records in the DataReader getting field values

'the Read method returns False when there are no more records

Do While objDataReader.Read()

strResult += "<tr><td>" & objDataReader("ISBN") & "</td><td> &nbsp;" _

& objDataReader("Title") & "</td><td> &nbsp;" _

& objDataReader("PublicationDate") & "</td><td></tr>"

Loop

'close the DataReader and Connection

objDataReader.Close()

objConnect.Close()

'add closing table tag and display the results

strResult += "</table>"

outResult.InnerHtml = strResult

Note

You could, of course, simply declare an ASP.NET list control such as a

DataGrid in the page, and then bind the

DataReader to the control to display the results. However, the technique used here to display the data demonstrates how we can iterate through the rowset.

Closing the DataReader and the Connection

You have to explicitly close the

DataReader . You also have to explicitly close the connection by calling the

Connection object's

Close method. Although the garbage collection process will close the

DataReader when it destroys the object in memory after the page ends, it's good practice to always close reader objects connections as soon as you are finished with them.

It's even more important to close the connection after you finish with it. Database connections are a precious resource, and the number available is usually limited. For this reason, as you'll see in the next section, ADO.NET provides a useful method that will close a connection automatically.

The CommandBehavior Enumeration

One useful technique to bear in mind when using a

DataReader is to take advantage of the optional parameter for the

Command object's

ExecuteReader method. It can be used to force the connection to be closed automatically as soon as we call the

Close method of the

DataReader object:

objDataReader = objCommand.ExecuteReader(CommandBehavior.CloseConnection)

This is particularly useful if you pass a reference to the

DataReader to another routine, say if you return it from a method. By using the

CommandBehavior.CloseConnection option, you can be sure that the connection will be closed automatically when the routine using the

DataReader destroys the object reference.

Other values in the

CommandBehavior enumeration that you can use with the

ExecuteReader method (multiple values can be used with

And or

+ ) are:

SchemaOnly : The execution of the query will only return the schema (column information) for the results set, and not any data. It can be used, for example, to find the number of columns in the results set.

SequentialAccess : Can be used to allow the

DataReader to access large volumes of binary data from a column. The data is accessed as a stream rather than as individual rows and columns, and is retrieved using the

GetBytes or

GetChars methods of the

DataReader .

SingleResult : Useful if the query is only expected to return a single value, and can help the database to fine-tune the query execution for maximum efficiency. Alternatively, use the

ExecuteScalar method of the

Command object.

SingleRow : Useful if the query is only expected to return one row, and can help the database to fine-tune the query execution for maximum efficiency.

Overall, the techniques used in this example are not that far removed from working with traditional ADO in ASP. However, there are far more opportunities available in .NET for accessing and using relational data. These revolve around the

DataSet rather than the

DataReader .

A Simple DataSet Example

A

DataSet is a disconnected read/write container for holding one or more tables of data, and the relationships between these tables. In this example, we just extract a single table from the database and display the contents.

Figure 8-10 shows what the Simple DataSet object example using an OLEDB Provider ( simple-dataset oledb.aspx ) sample looks like when it's run:

Figure 8-10:

The Simple DataSet Example Code

We've used the same connection string and SQL statement as in the

DataReader example. We also create a new

OleDbConnection object using this connection string as before:

Dim objConnect As New OleDbConnection(strConnect)

To execute the SQL statement for the

OleDbDataReader object in the previous example, we used the

ExecuteReader method of the

OleDbCommand object. In this example, to fill a

DataSet object with data, we use an alternative object to specify the SQL statement – an

OleDbDataAdapter object. Again, we provide the SQL statement and the active

Connection object as the parameters to the object constructor:

Dim objDataAdapter As New OleDbDataAdapter(strSelect, objConnect)

Note

This technique still creates and uses a

Command object. When you create a

DataAdapter , a suitable

Command instance is created automatically behind the scenes, and assigned to the

SelectCommand property of your

DataAdapter . You could do this yourself, but it would mean writing the extra code, and there is no advantage in doing so.

Now create an instance of a

DataSet object and then fill it with data from the data source by calling the

Fill method of the

DataAdapter object. Specify as parameters the

DataSet object and the name of the source table in the database:

Dim objDataSet As New DataSet()

objDataAdapter.Fill(objDataSet, "Books")

Filling the Schema in a DataSet

The

Fill method of the

DataAdapter object that was used here creates the table in the

DataSet , and then creates the appropriate columns and sets the data type and certain constraints such as the column width (the number of characters). It doesn't automatically set the primary keys, unique constraints, read- only values, and defaults. However, you can call the

FillSchema method first (before you call

Fill ) to copy these settings from the data source into the table:

objDataAdapter.FillSchema(objDataSet, SchemaType.Mapped)

After all this, you've now got a disconnected

DataSet object that contains the results of the SQL query. The next step is to display that data.

Displaying the Results

In this and many of the other examples, we're using an ASP

DataGrid control to display the data in the

DataSet object. You saw how the

DataGrid control works in Chapter 7:

<asp:datagrid id="dgrResult" runat="server" />

However, you can't simply bind the

DataSet object directly to a

DataGrid and have the correct rows displayed, as a

DataSet can contain multiple tables. One solution is to create a

DataView based on the table you want to display, and bind the

DataView object to the

DataGrid . You get the default

DataView object for a table by accessing the

Tables collection of the

DataSet and specifying the table name:

Dim objDataView As New DataView(objDataSet.Tables("Books"))

Then, assign the

DataView to the

DataSource property of the

DataGrid , and call the

DataBind method to display the data:

dgrResult.DataSource = objDataView

dgrResult.DataBind()

However, it's actually better performance-wise, though not as clear when you read the code, to perform the complete property assignment in one statement:

dgrResult.DataSource = objDataSet.Tables("Books").DefaultView

There is also a third option, as the ASP.NET Server Controls provide a

DataMember property that defines which table or other item in the data source will supply the data. So you could use:

dgrResult.DataSource = objDataSet

dgrResult.DataMember = "Books"

We use a mixture of techniques in our examples.

A Multiple Tables DataSet Example

Having seen how to use a

DataSet to hold one results table, you'll now see how to add multiple tables to a

DataSet object. The Multiple tables DataSet object example using an OLEDB Provider (

multiple- dataset-oledb.aspx ) example creates a

DataSet object and fills it with three tables. It also creates relationships between these tables.

As you can see in Figure 8-11, the page displays the connection string and the three SQL statements that extract the data from three tables in the database. Following this are two

DataGrid controls showing the contents of the

DataSet object's

Tables collection and

Relations collection. Further down the page (not visible here) are two more

DataGrid controls, which show the related data that is contained in the

Authors and

Prices tables within the

DataSet .

Figure 8-11:

The Multiple Tables DataSet Example Code

While the principle for this example is similar to the previous Simple DataSet example, the way we've coded it is subtly different. We've demonstrated another way of using the

Command and

DataAdapter objects.

As before, first create a

Connection object using your connection string, shown in the following code. However, this time create a

Command object next using the default constructor with no parameters, and then set the properties of the

Command object in a way similar to that used in traditional ADO.

Specify the connection string, the command type (in this case

Text , as a SQL statement is being used), and the SQL statement itself for the

CommandText property. By doing it this way, you can change the SQL statement later to get a different set of rows from the database without having to create a new

Command object.

'create a new Connection object using the connection string

Dim objConnect As New OleDbConnection(strConnect)

'create a new Command object

Dim objCommand As New OleDbCommand()

'set the properties

objCommand.Connection = objConnect

objCommand.CommandType = CommandType.Text

objCommand.CommandText = strSelectBooks

Once you have a

Command object, you can use it within a

DataAdapter . You need a

DataAdapter to extract the data from the database and squirt it into your

DataSet object. After creating the

DataAdapter , assign the

Command object to its

SelectCommand property. This

Command will then be used when you call the

Fill method to get the data:

So, you've got a valid

DataAdapter object, and you can set about filling your

DataSet . Call the

Fill method three times, once for each table you want to insert into it. In between, you have to change the

CommandText property of the active

Command object to the appropriate SQL statement, as shown in the following code:

'create a new DataAdapter object

Dim objDataAdapter As New OleDbDataAdapter()

'and assign the Command object to it

objDataAdapter.SelectCommand = objCommand

'get the data from the "BookList" table in the database and

'put it into a table named "Books" in the DataSet object

objDataAdapter.Fill(objDataSet, "Books")

'change the SELECT statement in the Command object

objCommand.CommandText = strSelectAuthors

'then get data from "BookAuthors" table into the DataSet

objDataAdapter.Fill(objDataSet, "Authors")

'and do the same again to get the "BookPrices" data

objCommand.CommandText = strSelectPrices

objDataAdapter.Fill(objDataSet, "Prices")

Opening and Closing Connections with the DataAdapter

In the examples that use a

DataAdapter , we haven't explicitly opened or closed the connection. This is because the

DataAdapter looks after this automatically. If the connection is closed when the

Fill method is called, it is opened, the rows are extracted from the data source and pushed into the

DataSet , and the connection is automatically closed again.

However, if the connection is open when the

Fill method is called, the

DataAdapter will leave it open after the method has completed. This provides you with a useful opportunity to maximize performance by preventing the connection being opened and closed each time you call

Fill (if you are loading more than one table in the

DataSet ). Just open the connection explicitly before the first call, and close it again after the last one, as shown by the highlighted lines in the following code:

Dim objDataSet As New DataSet()
objCommand.CommandText = strSelectBooks

objConnect.Open() objDataAdapter.Fill(objDataSet, "Books") objCommand.CommandText = strSelectAuthors objDataAdapter.Fill(objDataSet, "Authors") objCommand.CommandText = strSelectPrices objDataAdapter.Fill(objDataSet, "Prices")

objConnect.Close()

Adding Relationships to the DataSet

You've got three tables in your

DataSet , and can now create the relationships between them. Define a variable to hold a

DataRelation object and create a new

DataRelation by specifying the name you want for the relation ( BookAuthors ), the name of the primary key field ( ISBN ) in the parent table named Books , and the name of the foreign key field ( ISBN ) in the Authors child table.

Then add the new relation to the

DataSet object's

Relations collection, and do the same to create the relation between the

Books and

Prices tables in the

DataSet . As the relations are added to the

DataSet , an integrity check is carried out automatically. If, for example, there is a child record that has no matching parent record, an error is raised and the relation is not added to the

DataSet .

'declare a variable to hold a DataRelation object

Dim objRelation As DataRelation

'create a Relation object to link Books and Authors

objRelation = New DataRelation("BookAuthors", _

objDataSet.Tables("Books").Columns("ISBN"), _

objDataSet.Tables("Authors").Columns("ISBN"))

'and add it to the DataSet object's Relations collection

objDataSet.Relations.Add(objRelation)

'now do the same to link Books and Prices

objRelation = New DataRelation("BookPrices", _

objDataSet.Tables("Books").Columns("ISBN"), _

objDataSet.Tables("Prices").Columns("ISBN"))

objDataSet.Relations.Add(objRelation)

Displaying the Results

Having filled the

DataSet with three tables and two relations, you can now display the results. You use five

DataGrid controls to do this, as shown in the following code listing. The

DataSet object's

Tables and

Relations collections are bound directly to the first two

DataGrid controls, and for the tables within the

DataSet , we assign the

DataView returned by the

DefaultView property of the tables to the remaining three

DataGrid controls.

'bind the collection of Tables to the first DataGrid on the page

dgrTables.DataSource = objDataSet.Tables

dgrTables.DataBind()

'bind the collection of Relations to the second DataGrid on the page

dgrRelations.DataSource = objDataSet.Relations

dgrRelations.DataBind()

'create a DataView object to use with the tables in the DataSet

Dim objDataView As New DataView()

'get the default view of the Books table into the DataView object

objDataView = objDataSet.Tables("Books").DefaultView

'and bind it to the third DataGrid on the page

dgrBooksData.DataSource = objDataView

dgrBooksData.DataBind()

'then do the same for the Authors table

objDataView = objDataSet.Tables("Authors").DefaultView

dgrAuthorsData.DataSource = objDataView

dgrAuthorsData.DataBind()

'and finally do the same for the Prices table

objDataView = objDataSet.Tables("Prices").DefaultView

dgrPricesData.DataSource = objDataView

dgrPricesData.DataBind()

A User Control That Returns a DataSet Object

The preceding code is used in several examples in this and subsequent chapters, and to make it easier we've encapsulated it as a user control that returns a fully populated

DataSet . Change the page's file extension to

.ascx and change the

Page directive to a

Control directive:

<%@Control Language="VB"%>

Then, instead of placing the code in the

Page_Load event handler, place it in a

Public Function to which you provide the connection string and the

WHERE clause for the SQL statement as parameters. The function returns a

DataSet object, as shown in the following code. Note that the parameters passed to this function allow you to select a different set of books by varying the

strWhere parameter value when you use the control.

Public Function BooksDataSet(strConnect As String, _

strWhere As String) _

As DataSet

...

strSelectBooks = "SELECT * FROM BookList WHERE " & strWhere

strSelectAuthors = "SELECT * FROM BookAuthors WHERE " & strWhere

strSelectPrices = "SELECT * FROM BookPrices WHERE " & strWhere

Dim objDataSet As New DataSet()

...

... code to fill DataSet as before ...

...

Return objDataSet

End Function

The Using a control that creates and returns a DataSet object (

use-dataset-control.aspx ) example page contains the

Register directive and matching element to insert the user control containing the function just described into the page. Then, to get a

DataSet from the control, just create a variable of the correct type and set it to the result of the

BooksDataSet method – specifying the values for the connection string and

WHERE clause parameters when you make the call.

<%@ Register TagPrefix="wrox" TagName="getdataset"

Src=" %>

...

<wrox:getdataset id="ctlDataSet" runat="server"/>

Dim objDataSet As DataSet

objDataSet = ctlDataSet.BooksDataSet(strConnect, "ISBN LIKE '0764544%'")

The investigation of the

DataSet object will be continued in Chapters 9 and 10. You'll see how to use more complex data sets, and update and edit data using the ADO.NET relational data access classes. We'll also explore the ways that .NET combines the traditional relational database access techniques with the more recent developments in XML-based data storage and management.