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

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

Đang tải... (xem toàn văn)

Thông tin tài liệu

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 be able to initialize the properties of the object if memory hadn't been allocated. The important point is that, even if you throw an exception in the constructor, the object already exists in memory and is accessible to the client code. Take a closer look at the ReadValuesFromDataRow method. The technique used here allows consumers of this class to modify the object's properties (the class-level variables) without modifying the values in the data row, and thus the dataset. In short, the data row holds the original state of the Customers row, whereas the class-level variables hold the current state. At the moment, the ReadValuesFromDataRow method is just used to initialize the class, but, because it sets the class-level variables to the state of the data row, you could also use it as the basis of a cancel or reset method. Remember: The dataset stores data as XML, and it does not require an active database connection. With ADO, retaining recordsets and rows in memory results in a heavy load on your database servers. With ADO.NET, the cost is minimal. In the next section, you will add a matching method that writes the current state of the object to the data row just prior to saving changes to the database. Comments Inheritance is another OO term that simply means that all the members of one class are now part of another class. Instead of copying and pasting code from one module into another module like you would do with Visual Basic 6 (the copy-and-paste method is sometimes referred to in the OO world, jokingly, as "Editor Inheritance"), you simply add a reference to the class containing the code you want to use to the class declaration of another class. One of the most fundamental problems in Visual Basic database development is how to interpret null values in the database within Visual Basic code, and viceversa. In the database, a null value is a null value, and it won't cause database runtime errors (unless nulls are not allowed). In Visual Basic, base datatypes, such as strings and dates, do not have null states. You can, however, have a null pointer in Visual Basic .NET, which means that the variable was never initialized. An uninitialized variable is quite different from a database null value and could just as easily signify a runtime bug as a null value. In either case, other developers would have to include error handling for NullPointerExceptions. In Listing 9.24, I used 0-length strings to represent null. This is, perhaps, a bad habit held over from Visual Basic 6. The definition of a null value is, incongruously, undefined. A null value represents nothing-not even a datatype. In other words, null represents the absence of data. A 0-length string represents a specific datatype and a specific value-that is, a string of no characters. In short, a null value and a 0-length string are two entirely different things. The issue becomes more complex when you add numbers, Booleans, dates, and so on. Does 0 represent null? Does January 1, 1901 represent a null for a date? When you begin to write production applications in .NET, you should make a choice about how you will handle null values and stick to it. Microsoft has made the choice in .NET that a null is a null and nothing else, and, as you can see from the ReadValuesFromDataRow method, all the code that is generated in .NET provides methods to check if a value is null, as well as methods to set a value to null. You might want to follow this recommendation, but to keep the examples in this chapter as simple as possible, we will continue to use 0-length strings to represent null values. . 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,. 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

Ngày đăng: 24/12/2013, 06:17

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan