Tải bản đầy đủ (.pdf) (7 trang)

Tài liệu Control the Creation and Behavior of Classes Now that you have code that can prepare all of the pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (21.57 KB, 7 trang )

9.4 Control the Creation and Behavior of Classes
Now that you have code that can prepare all of the objects you need to access and update
data in the database, it's time to write code that can load information for the database into
your class. In Visual Basic 6, developers typically used one of two techniques to populate
a class from a database. One was to have a collection class with a LoadData method that
would open a recordset and iterate through the rows, creating instances of the class
representing the table and setting the object's properties with data from the row. The
second was to have a Retrieve method that would accept a primary key value and load the
data from that one row.
The problem with both of these techniques was that you could never be sure if a
developer who was using your component would call the methods in the proper order.
For example, that developer might attempt to access properties of the class before calling
the Retrieve method. If those properties were strings and numbers, the developer would
receive a 0-length string and a 0, respectively. These could both be valid values within
your database and would not raise an error. How do you control what is happening? You
could have an internal Boolean variable representing whether data was loaded into the
object, and add If...Then statements in every property and method that would raise an
error if that Boolean variable was False. This is not, however, a neat solution.
The Class_Initialize Event was no help either because it wouldn't accept parameters. You
could use a global variable that the initialize event would access, but this would only
work if a single instance of the class could exist in your application.
In short, in VB 6, you didn't have control over the creation of objects.
Technique
Visual Basic .NET does give you this kind of control with a new kind of method called a
constructor. A constructor is a method that is called in conjunction with the New
keyword when you're creating an object instance. It is similar to the Class_Initialize event
that is available in Visual Basic 6, except that it accepts parameters. If the parameters that
are passed to a constructor are not of the proper type, or if a developer neglects to specify
those parameters, the code does not compile.
In this section, you will learn how to write a constructor that accepts a primary key value
and uses that value to populate the properties of a CCustomer object.


Steps
First, the CCustomer class needs access to the data adapter code you created in the
previous section. You could paste all of the code you have written so far in the
CCustomer class into the CCustomerData class, but you may want to use the
CCustomerData objects elsewhere. That leaves you two options: you could have a
module-level CCustomerData variable within the CCustomer class, or you could have the
CCustomer class "inherit" all of the code in the CCustomerData class. You've probably
done the former before, so try the latter.
1. In the declaration of the CCustomer class, add "Inherits CCustomerData".
2. Add module-level variables of type dsCustomers and
dsCustomers.CustomersRow, and name them mdsCust and mdrCust, respectively.
3. Add the following constructor to the CCustomer class, as shown in Listing 9.23.
Listing 9.23 frmHowTo9_4.vb: A Constructor for the CCustomer Class
Public Class CCustomer
Inherits CCustomerData
Implements ICustomer

#Region "Constructors"
Public Sub New(ByVal pCustomerIDToRetrieve As String)
' This constructor takes a valid CustomerID as a parameter,
' and retrieves that row. If the row does not exist, an
' Exception is thrown.

' Create an instance of the Customers dataset
mdsCust = New dsCustomers()

Try
' Set the parameter for the select command so that we retrieve
' the proper row from the table.


' MyBase is used to refer to members of the inherited class.
' It is only required in certain circumstances, such as when
' calling a constructor in the inherited class.
MyBase.odaCustomers.SelectCommand.Parameters(0).Value = _
pCustomerIDToRetrieve

' A strongly typed dataset could contain multiple tables,
' so you must specify the table name to fill.

odaCustomers.Fill(mdsCust, "Customers")
Catch ex As Exception
Throw New ApplicationException("The customer row could not be
retrieved." & ex.Message)
End Try

If mdsCust.Customers.Rows.Count = 1 Then
' Option Strict disallows implicit type conversions.
' Even though the row returned by dsCustomers.Rows(0) is
' actually of the type CustomersRow, the method is declared
' as returning System.Data.DataRow.
mdrCust = CType(mdsCust.Customers.Rows(0),
dsCustomers.CustomersRow)
ReadValuesFromDataRow()
Else
' Throw an exception if no data was returned
Throw New ApplicationException("The customer " & _
pCustomerIDToRetrieve & " was not found.")
End If

End Sub

#End Region
Note
According to Microsoft, you should use ApplicationException for
all exceptions that custom applications throw. This is because
exceptions are typically defined as one of two groups: system
exceptions and application exceptions. System exceptions are
exceptions that are related to execution of code and the .NET run-
time, including things as standard as NullPointerExceptions as
well as more critical issues, such as OutOfMemoryExceptions.
ApplicationExceptions are designed for unexpected circumstances
in your code. If you only throw exceptions, it will be more
difficult for other developers to determine how severe the error is
and how to recover properly.
4. Finally, add the private method called ReadValuesFromDataRow from Listing
9.24 that reads the values from the data row and writes to the class properties.
ReadValuesFromDataRow translates null values from the database into values that
don't cause NullPointerExceptions in Visual Basic code.
Listing 9.24 frmHowTo9_4.vb: Excerpts from the ReadValuesFromDataRow
Method
Private Sub ReadValuesFromDataRow()
With mdrCust
' Because the CustomerID and the CompanyName are both required
' values, we will not have handling for zero-length strings.
mstrCustomerID = .CustomerID
Me.CompanyName = .CompanyName

If .IsContactNameNull Then
Me.ContactName = ""
Else
Me.ContactName = .ContactName

End If

If .IsContactTitleNull Then
Me.ContactTitle = ""
Else
Me.ContactTitle = .ContactTitle
End If

End With
5. To test this code, you need to add code to the click event of the Retrieve button to
use the constructor, as well as a method that copies the values of the object to the
form's text boxes. Listing 9.25 shows the code behind btnRetrieve, as well as the
GetProperties to write the object's properties to the text boxes.
Don't forget that you originally declared the mCustomer variable by creating a
new instance of the class. That declaration called the class's default, parameterless
constructor, which the .NET runtime creates for you if you do not declare a
constructor. As soon as you do declare a constructor, however, that default,
parameterless constructor will no longer exist. You will need to change the
declaration to exclude the instantiation of a new Ccustomer class, as written at the
beginning of Listing 9.25.
Listing 9.25 frmHowTo.vb: Testing the New Constructor
Private mCustomer As CCustomer

Private Sub btnRetrieve_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnRetrieve.Click

Try
mCustomer = New CCustomer(Me.txtCustomerID.Text)
GetProperties()
Catch ex As Exception

MsgBox(ex.Message)
End Try
End Sub

Private Sub GetProperties()

Me.txtAddress.Text = mCustomer.Address
Me.txtCity.Text = mCustomer.City
Me.txtCompanyName.Text = mCustomer.CompanyName
Me.txtContactName.Text = mCustomer.ContactName
Me.txtContactTitle.Text = mCustomer.ContactTitle
Me.txtCountry.Text = mCustomer.Country
Me.txtCustomerID.Text = mCustomer.CustomerID
Me.txtFax.Text = mCustomer.Fax
Me.txtPhone.Text = mCustomer.Phone
Me.txtRegion.Text = mCustomer.Region
Me.txtPostalCode.Text = mCustomer.PostalCode

End Sub
How It Works
By adding this constructor, other developers must provide a CustomerID when they
attempt to create an instance of the CCustomer class. If a corresponding row is in the
Customers table, the properties of the class are populated using data from the database. If
not, an exception is thrown that notifies the developer of the problem.
Of course, a developer could ignore your exception and attempt to access the properties
of the object, but you did give those other developers ample warning. Also, none of the
class-level variables would have been instantiated. In Visual Basic 6, this would not have
made a difference; base datatypes have a default value. (Strings are 0-length strings,
numbers are 0, and so on.) In Visual Basic .NET, however, base datatypes are objects just
like everything else, so a NullPointerException will be thrown at runtime if a developer

should churlishly ignore your previous exception.
Note
Exactly when a constructor executes can be a little confusing. Two steps
are involved in creating an object with a constructor. (Technically, all
objects have at least one constructor, even if you didn't implement one.)
First, the .NET runtime allocates space in memory for the object and
returns a pointer to your code. Second, before control is passed back to
your code, the constructor is executed. Why? Your constructor wouldn't

×