ADO .NET Introduction - Professor Jerry Post

Download Report

Transcript ADO .NET Introduction - Professor Jerry Post

.NET Web Forms
ADO .NET Introduction
© 2002 by Jerry Post
1
ADO .NET Overview
Major question: What
is missing? Hint: it is
missing in ADO .NET,
not just the diagram.
ASP .NET Code
Data Connector
(Establishes connection and
opens the database.)
Answer: save data
Data Adapter
Web Form
Data Controls
Database
(Translates commands and
exchanges data.)
Fill
Data Set (Collection of tables and relationships.)
Data Bind
Data Table
*CustomerID
LastName
FirstName
Data View (Based on 1 table.)
ORDER BY LastName
WHERE …
Data Relationship
(Associations among tables.)
2
Sample Project: Simple2
 You should create a new sample project to test your skills.
 The completed version should look something like: Simple2
3
Data Connector
 The Data Connector is usually straightforward.
 The wizard starts automatically when you add a Data Adapter.
 Choose the type of database (that picks the provider and the specific
connector).
 Enter the connection information—usually location.
 Access: Specific physical path.
 Oracle: Oracle connection information
 Registered name
 User name
 User password
 SQL Server connection information (but use the SQL Adapter)
 Server
 User name
 User password
 SQL Server has many shortcuts and tricks within Visual Studio
 The connector simply establishes the communication links to the
database.
4
Data Connector Challenges
 ADO .NET difficulties
 Access problem is that it is hard to change the location later.
 It is also difficult to change databases later (SQL Server to Oracle).
 Partial Solution (but critical)
 Store all DB specific code in one place.
 Do not scatter database code across your application and forms.
 Before building any database forms, create a new blank Component
for the sole purpose of holding all primary database definitions.
 Solution Explorer + Add + Add New Item + Component Class: DBMain
 To create a database connection, simply drag one onto the design
screen of the new component, and fill in the wizard properties.
 Select a provider
 Access: Jet 4.0
 Oracle: Oracle Provider for OLE DB (maybe Microsoft version)
 SQL Server: Use the SQL Adapter instead!
 Rename the connector: cnnOle
5
TMF: Things Microsoft Forgot
 I developed some standard routines to handle several common
issues. They are stored in a class called TMF.
 You will eventually end up adding this class to most of your forms.
 First, add the file to your project, right-click + Add + Add Existing:
TMF.vb (you only have to do this once).
 Second, for any form that you need the routines, open the Code View
(View + Code, or right-click); add one line of code, often at the top,
along with the control definitions:
Dim tmf As TMF = New TMF()
 Details of the routines will be explained in future slides as they are
needed.
 Notes and some documentation are written as comments in the
file.
6
Data Connector: Access Flexibility
 One problem with the Access connector is that it relies on a fixed
pathname, making it difficult to move the database.
 You need to add the special TMF (Things Microsoft Forgot) component to
your project (Add + Existing File).
 In the DBMain component, expand the Component Designer Generated
region, in the Public Sub New(), after InitializeComponent, add
Dim tmf As New TMF()
Dim strCS As String
strCS = tmf.GetCnnString("cnnOleCareer")
If (strCS <> "") Then Me.cnnOleCareer.ConnectionString = strCS
 In the Web.config file, right before the end </configuration>, add/modify
<appSettings>
<!-- User application and configured property settings go here.-->
<!-- Example: <add key="settingName" value="settingValue"/> -->
<add key="cnnOleCareer.ConnectionString" value="" />
<add key="cnnOleCareer.DataSource" value="../Database/career.mdb" />
</appSettings>
 This approach uses a relative path, so you can move the entire project.
7
Data Adapter Comments
 Data Adapter
 Is responsible for translating SQL requests to the chosen type of
database and returning data rows and messages.
 Every command or table that you want to access must be associated
with a data adapter.
 Technically, a data adapter can handle several different tables.
 But, unless the tables will be related (parent/child), it seems easier to
create a separate data adapter for each table.
 Biggest drawback is that SQL Server uses a different data adapter
than Access/Oracle. So, if you want to change DBMSs later, you have
to replace all of the adapters!
 Critical: Keep all data adapters in one (maybe two) locations: the
DBMain component.
 Creating a data adapter
 From the Data toolbox, drag an adapter onto the main DB component.
(Ole for Access or Oracle, Sql for SQL Server).
 Answer the wizard questions. To change later: Configure Data Adapter
8
Create Data Adapter: Query Type
 First, choose your database
connection (not shown here).
 Second, choose the type of
query to use.
 Access only supports text
SQL.
 SQL Server and Oracle will
generally run faster if you use
a stored procedure, which is
a parameterized view stored
and precompiled on the
database server.
 If you let the wizard build the
procedure, you should go
back to the database and
double-check it.
9
Create Data Adapter: Query Type
 Create the SQL statement to
retrieve the data from tables
or existing views.
 Use the Query Builder to
minimize typing errors.
 Warning: if you pull data from
more than one table (or from a
view that uses more than one
table), .NET will not be able to
edit that data.
 Query Builder (next slide)
 Advanced Options (slide)
10
Data Adapter: Query Builder
 Add tables
 Click columns to add: Warning,
they are displayed in the order
in which you add them. You
can move them later, but it is
easier to do it right now.
 Note that columns that are
reserved words should use
brackets. When in doubt, use
brackets.
 If you use more than one table,
.NET will not be able to edit the
data.
 If you JOIN tables, do it in the
database itself and use a
simple query here to pull from
that view. Portability!
11
Data Adapter: Advanced Options
 If you only need to retrieve data (not
change it), uncheck all of these
options.
 In some situations, you will want to
skip the optimistic concurrency. If
you uncheck this box, all changes
will be written to the database, even
if someone else changed the data
immediately before you.
 The third option is only available
with SQL Server and Oracle
(databases that support query
languages that can do more than
one thing). Useful if you insist on
using Identity (AutoNumber)
columns and need to insert rows.
12
Data Set Overview
 A Data Set is roughly a miniature in-memory copy of part of your
database.
 You specify which tables you want to include.
 You can build relationships (usually parent/child).
 You can create Data Views, but data views are not queries. They
cannot perform JOINs. They can only sort and filter a single data table.
 Technically, a data set can contain multiple tables, but that feature
only seems to be useful if you have related tables. Even then, it is
not as useful as you might think.
 Major issue: the data set is an in-memory copy of your database.
 The server has to hold the data in memory.
 Once you send your page to the user, the data set is discarded by the
server. If you want to keep it for postback/roundtrips, you have to store
the dataset some place. Session variable?
 Bottom line: big data sets are a disaster!
13
Data Set: Generate
 Once the adapter is created, it is
easy to generate a data set in the
main DB component.
 Right-click on the adapter.
 Generate Data set
 Select New and enter a unique
data set name.
 Make sure the proper table is
checked.
 Check the box to add it to the
designer.
 If you need to delete a dataset,
you will have to delete it from the
.xsd list of files (and delete it from
the DB code).
14
What We Have So Far
 A Data Connection that can open the database
 A Data Adapter that can send SQL to the database and receive
data rows.
 A definition of a Data Set that can temporarily hold data that is read
from one table (or view).
 You can add other adapters and datasets.
 You can create relationships if there are parent/child tables, but you
will see that these are not very useful.
 Things we do not have yet.
 There is no data in the data set.
 The data set is not connected to the form
15
Filling the Data Set
 In the main DB component (View + Code) add a subroutine:
Public Sub FillEmployee(ByVal ds As DataSet)
adpEmployee.Fill(ds)
End Sub
 At first glance, the routine seems pointless, but its purpose is to
keep the database-specific code in one component. Remember
that the adapter is specific to the database. This way, your form
can use the dataset only, while the adapter stays in the one
component.
 We will add code to your data form to call this routine.
16
Creating the Data Form
 Three basic types of forms are commonly used in business
applications. Each has slightly different considerations and setup.
They will be covered in later lessons.
 Consider a standard form with one row of data.
 To start, add a new blank Web Form, named Employee
 Drop a Data Set onto the blank design. Select the employee data
set you created earlier. Then do View + Code to see the code.
 You need to tell the form to go to the database and fill the data set
with data. There are two possible times/places to do this:
Page_Load or Page_Init. For now, Page_Load is easier.
 Near the top of the class, add:
Protected cmpMain as New DBMain()
Protected tmf As New TMF()
 In the Sub Page_Load, add:
cmpMain.FillEmployee(DsEmployee1)
17
Starting the Data Form Page
 Add a style. Open the style sheet and add a class for PageTitle
 Choose a font family (e.g., Arial) and font size (e.g., 2 em)
 In Text, set the horizontal alignment to Centered
 Save and close the style sheet and drop it onto the data form.
 Add a Title. From the Web Forms toolbox,




Drop a Label near the top of the form
In the Text property, enter the title: Employee
Set the CssClass: PageTitle
Change the ID: lblPageTitle (not strictly required, but a useful habit)
 Link the page. Open the main form (rename it, e.g., AAStart)
 Add the style sheet and a title.
 Add a HyperLink that opens the data form.
 Set: Text (Employee Form), ID (lnkEmployee), NavigateURL
(Employee.aspx)
 Save everything and build and browse to test it. If the DB form
crashes, check the security settings first.
18
Adding Data Controls
 Design view on the data form.
 Drag and drop labels and text boxes onto the form.
 Keep a list handy for the data columns.
 For labels, set properties
 ID:
 Text:
lblColumnlblEmployeeID
Column
Employee ID
 For text boxes, set properties
 ID:
txtColumn
 Tab Index
txtEmployeeID
Number from top to bottom (0 … 5)
 Add a Button, Call it Save (ID=btnSave) (Do not write code for it yet).
 Use Format Menu to vertically space and align everything.
 Data binding the controls
 For each text box, click the Data Binding property
 Choose simple binding, and click your way down the list until you can
select the matching column. (See next slide)
19
Simple Data Binding
Make sure Text
property is selected
on the left.
Click your way down
the list on the right
until you can select
the matching data
column.
You can set a
format, for example
for dates.
20
Activate Data Binding
 The control properties simply set a mapping from the dataset
table to the control.
 You still need to activate the binding and transfer the data.
 Each control has a DataBind method that performs this one-way
transfer.
 But, it is easier to use the Page.DataBind method because it does the
transfer for all of the controls.
 The problem:
 Calling DataBind replaces the content of target, destroying whatever
was there before.
 Forms use a PostBack approach. When users click a button, the data
is sent back to the same page. You must avoid destroying the data
changed by the user.
 In Page_Load, add:
If Not Page.IsPostBack Then
 Test it
Page.DataBind()
End If
21
Saving Data: The Simple Approach
 When you created the data adapter, by default, it created SQL
statements to Update, Insert, and Delete.
 If the data in the in-memory data set changes, ADO .Net can
automatically apply the changes (including insert and delete) back to
the stored database.
 First, in the Main database component, add:
Public Sub UpdateEmployee(ByVal ds As DataSet)
adpEmployee.Update(ds)
End Sub
 In the data form, write code for the Save button to transfer the form
data to the data set. Double-click the Save button to start. Add:
DsEmployee1.Tables("Employee").Rows(0).BeginEdit()
DsEmployee1.Tables("Employee").Rows(0)("EmployeeID") = txtEmployeeID.Text
DsEmployee1.Tables("Employee").Rows(0)("LastName") = txtLastName.Text
… (do all of the columns)
DsEmployee1.Tables("Employee").Rows(0).EndEdit()
cmpMain.UpdateEmployee(DsEmployee1)
22
Problems with the Simple Approach
 We have ignored concurrency issues.
 It would actually be better if you build the dataset without testing for
concurrency.
 Right now, if someone else changes the data, the update will crash, and
cannot be recovered unless you back out of the form and reload the data
from the database.
 However, notice that every time the form is loaded, including during post
back, the page goes to the database and gets the current version. So, this
version will actually overwrite most changes made by other people with no
warning. The only concurrency issue that will be caught and crashed is on
the server between re-reading the page and writing the new data; which is
a relatively minor chance.
 It will be incredibly boring to duplicate these steps for every form you
will have. It will also be painful to return later and make changes.
 Note that the dataset is not saved across post back. Which is probably
good, since the system is designed to read the entire table. Which is
crazy, since the form only uses one row at a time.
23
Customer Form: A Better Method
 It is pointless and painful to have to write the same complex update
code every time you need a form. So, I created some generic routines
to help. This example relies heavily on them.
 Make sure you have the TMF file in your project.
 Copy the generic code from the DBMain in the sample project. (especially
BatchUpdate and MergeConcurrency)
 Getting started
 Create the Data Adapter in DBMain as usual, be sure to take the default to
generate optimistic concurrency.
 Generate the Data Set as usual. Keep a copy of the column names.
 Add two routines to the DBMain code:
Public Sub FillCustomer(ByVal ds As DataSet, ByVal sID As String)
Dim tmf As TMF = New TMF()
adpCustomer.SelectCommand.CommandText =
tmf.AddWhereToSelect(adpCustomer.SelectCommand.CommandText, "CustomerID=" & sID)
adpCustomer.Fill(ds)
End Sub
Public Function UpdateCustomer(ByVal ds As DataSet, ByVal sID As String) As String
Return BatchUpdate(ds, adpCustomer, "Customer", AddressOf Me.FillCustomer, sID)
24
End Function
Customer Form: Construction
 Create a new Web Form for Customer.






Add the style sheet and Page Title
Add the data set
Add the labels and text boxes, set the tab index
Use DataBinding to link the text boxes to the dataset.
Add the Save button (do not write code yet)
Add a blank Label at the top, across the page, ID: lblMessage
 View + Code, below the control definitions (Protected WithEvents )
Protected cmpMain As DBMain = New DBMain()
Protected tmf As tmf = New tmf()
25
Customer Form: aryColumns
 The generic code needs a method to map the column names to the
controls. The easiest method is to create a special array above the
Page_Load routine. For each column, you enter




Column Name
Control ID
Type of control (Text, Label, DDL, Check)
Whether or not the column is part of the primary key
Private aryColumns() As tmf.SingleEditItem = { _
New tmf.SingleEditItem("CustomerID", "txtCustomerID", "Text", True), _
New tmf.SingleEditItem("FirstName", "txtFirstName", "Text", False), _
New tmf.SingleEditItem("LastName", "txtLastName", "Text", False), _
New tmf.SingleEditItem("Address", "txtAddress", "Text", False), _
… Do all of the columns
…
New tmf.SingleEditItem("EMail", "txtEmail", "Text", False), _
New tmf.SingleEditItem("URL", "txtURL", "Text", False) _
}
26
Customer: Page_Load
 The main change to Page_Load is that it only reads from the
database the first time the page is loaded (not during PostBack),
and the dataset has to be saved in a Session so we can hang onto
the original values.
 Also notice that the Fill routine has been modified to read only one
row of data.
If Not Page.IsPostBack Then
cmpMain.FillCustomer(DsCustomer1, "1")
Session("dsCustomer1") = DsCustomer1
Page.DataBind()
Else
DsCustomer1 = CType(Session("dsCustomer1"), dsCustomer)
End If
27
Customer: btnSave
 Saving the data is now handled by the generic routines. Add the
following code to the btnSave_Click routine:
tmf.CopyFormToDataSet(Me, aryColumns, DsCustomer1, "Customer", 0)
lblMessage.Text = cmpMain.UpdateCustomer(DsCustomer1)
 The first routine copies the data from the form to the dataset (using the
aryColumns mapping).
 The second routine does the update, and checks for concurrency.
 What happens if there is a concurrency collision? Try it.
 Where are the underlying values stored? They are kept as properties
within the dataset table.
 The routine re-reads the database, and replaces the underlying values
with the new current values. However, it leaves this user’s changes on the
form. So clicking Save a second time will force update the database if
there was a concurrency error.
 You really need to add some message and probably more choices to the
user. But that is a user interface decision.
28
Customer: State DropDownList
 To provide a drop down list (selection box) for states, you first need to
create a table of StateCodes, with the state and the name. Users will see
the names (alphabetized), and the two-letter state code will be inserted
into the database.
 Second, create a new data adapter and dataset.
 Retreive StateCode and Name, sorted.
 Deselect the build update.
 Create the FillStateCodes routine.
 Customer form, delete the existing state text box








Add the StateCodes data set, then add a DDL and set its tab index
ID
ddlState
DataSource
DsStateCodes1
DataMember
StateCodes
DataTextField:
Name
DataValueField
State
In the aryColumns array, change the control type to “DDL” and ID to ddlState
In Page_Load, fill the new dataset
cmpMain.FillStateCodes(DsStateCodes1)
29
DDL: Tricky TMF
 Microsoft did not bother to create a simple mechanism to get drop
down lists to display the currently assigned value. For instance, if
the database state value is CT, you want the box to start with a
display of Connecticut.
 So, turn to the trusty TMF class for help.
 In properties for the DDL, click the Data Bindings button.
 Make sure you choose the SelectedIndex on the left side!
 Choose custom binding, and add: (It’s that easy, see next slide)
tmf.SelectDefaultDDL(sender, DsStateCodes1, DataBinder.Eval(DsCustomer1,
"Tables[Customer].DefaultView.[0].State"))
Dataset name that holds the DDL list
Dataset name for the overall form
Column name from the overall form that holds the default value
Table name for the overall form
30
DDL Binding for SelectedIndex
31
Customer Form
32
The Form is Still not Usable
 The generic code can be used for any form. The aryColumns array
is the key to providing the information the subroutines need.
 You really need to think about a user interface to explain how to
handle concurrency collisions. The grid code (later) shows one way
to handle the problem. But feel free to experiment, there are no
great solutions yet.
 Several steps remain to make this a useful form.






Selecting a customer/row (tricky, depends on user interface goals)
Finding a customer (moderately tricky code)
Adding a new customer (relatively easy)
Deleting a customer (easy, until you have to show the next customer)
Security (relatively easy, but requires more forms)
More?
33
Concurrency
 How does the update command handle optimistic concurrency?
 The SQL command uses the original value for each column in its
Where clause.
 But where does it get (and store) the original value, which is the value
that existed when the data row was retrieved?
 Answer: the data row holds multiple versions of each data column.
You can access them with DataRowVersion enumeration.
 If there is a concurrency problem, the BatchUpdate routine
retrieves the current value and places it into the “Original” value. It
leaves the current user’s value as the primary/current value. You
can compare these values in each column to see where the
problem was and show both values.
‘ Set ID= the primary key value of the row being edited
Dim dsRow As DataRow = DsMine1.Tables(“MyTable”).Rows.Find(ID)
‘ Check each column/loop
If (dsRow(j) <> dsRow(j, DataRowVersion.Original)) Then
‘ Display concurrency error/value
End If
34
Customer: Add Code
Public Function AddCustomer(ByVal ds As DataSet) As String
Dim cID As Integer = GenKey("Customer")
Dim strMsg As String
If (cID < 0) Then
strMsg = "Unable to generate a new Customer ID."
End If
Dim dr As DataRow = ds.Tables("Customer").NewRow()
dr("CustomerID") = cID
ds.Tables("Customer").Rows.Add(dr)
strMsg &= UpdateCustomer(ds, cID)
ds.Clear()
' Clear the data set and reload just the one new row
ds.AcceptChanges() ' Need to tell it not to delete the original row
FillCustomer(ds, cID)
Return strMsg
End Function
GenKey is another generic routine you need to add to your
DBMain. If you are running under Oracle, rewrite GenKey to
use Sequences.
35
Customer: Delete Code
 The Delete code is much trickier, because we need to guess which
CustomerID to display next, and bad things happen to the form if
we delete all of the rows in the dataset.
Public Function DeleteCustomer(ByVal ds As DataSet) As String
Dim sID As String = ds.Tables("Customer").Rows(0)("CustomerID")
Dim strMsg As String
' The hard part: What customer do I load next?
' Try finding just the first customer sorted by ID
Dim rdr As OleDb.OleDbDataReader
Dim cmd As OleDb.OleDbCommand = New OleDb.OleDbCommand()
cmd.CommandText = "SELECT CustomerID FROM Customer ORDER BY CustomerID"
cmd.Connection = Me.cnnOleSimple
cnnOleSimple.Open()
Dim cID As Integer
36
Customer: Delete Code 2
Try
rdr = cmd.ExecuteReader(CommandBehavior.Default)
rdr.Read()
cID = CType(rdr("CustomerID"), Integer)
If (cID = CType(sID, Integer)) Then ' If delete 1st one, try to get the next row
rdr.Read()
cID = rdr("CustomerID")
End If
rdr.Close()
Catch e2 As Exception
cID = -1
strMsg = "No customers remain. Please add at least one."
End Try
cnnOleSimple.Close()
37
Customer: Delete Code 3
Try
ds.Tables("Customer").Rows(0).Delete()
If (cID <> -1) Then
UpdateCustomer(ds, cID.ToString)
Else
UpdateCustomer(ds, Nothing)
End If
strMsg &= "Deleted customer " & sID
Catch e As Exception
strMsg = "Error deleting customer. " & e.Message
End Try
If (cID = -1) Then ' Dataset can never be empty or form will crash
Dim dr As DataRow = ds.Tables("Customer").NewRow
dr("CustomerID") = -1
ds.Tables("Customer").Rows.Add(dr)
End If
Return strMsg
End Function
38
Summary
 The form is getting closer.
 It still needs to be within an application, that can handle things like
selecting a customer and security.
 But, building several forms of this type for an application is still
going to be painful, with lots of repetition.
 Ultimately, it would be nicer if a data wizard existed to build most of
the generic code automatically.
 Lacking the time to write such a wizard, the next best step is to
create a set of generic templates, that can be copied and used to
quickly build forms that handle standard problems.
39