Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Developer's Handbook (2002) [eng]

.pdf
Скачиваний:
36
Добавлен:
16.08.2013
Размер:
4.94 Mб
Скачать

if ((bool)Data[12, 0]) cbIsIn.Checked = true;

else

cbIsIn.Checked = false;

}

private void DoDataUpdate()

{

//Change the field values using the easy, set_Collect()

//method. You must provide a valid name and update value. DBRecordset.set_Collect("Name",

txtName.Text); DBRecordset.set_Collect("Rating",

txtRating.Text); DBRecordset.set_Collect("Description",

txtDescription.Text); DBRecordset.set_Collect("LeadActor",

txtLeadActor.Text); DBRecordset.set_Collect("LeadActress",

txtLeadActress.Text); DBRecordset.set_Collect("SupportingCast",

txtSupportingCast.Text); DBRecordset.set_Collect("Director",

txtDirector.Text); DBRecordset.set_Collect("Producer",

txtProducer.Text); DBRecordset.set_Collect("Notes",

txtNotes.Text); DBRecordset.set_Collect("DatePurchased",

DateTime.Parse(txtDatePurchased.Text)); DBRecordset.set_Collect("Format",

txtFormat.Text); DBRecordset.set_Collect("IsIn",

cbIsIn.Checked);

}

private void DoAdd()

{

// Create a list of fields to retrieve. Object []FieldArray =

new Object[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

Object []DataArray =

new Object[] {"No Name Assigned", "NR",

"No Description Supplied", "No Lead Actor",

"No Lead Actress", "No Supporting Cast", "No Director",

"No Producer",

"No Special Notes for this Film", DateTime.Today,

"VHS",

true};

//Add a new record to the table. DBRecordset.AddNew(FieldArray, DataArray);

//Update the recordset to reflect the new record.

DBRecordset.Requery(0);

DBRecordset.MoveLast();

// Display the record on screen for editing. DoFill();

}

private void DoDelete()

{

//Delete the current record. DBRecordset.Delete(AffectEnum.adAffectCurrent);

//Update the recordset to reflect the deleted record. DBRecordset.Requery(0);

//Display the next record on screen.

DoFill();

}

You'll find that ADO provides more than one way to perform every task. The example uses the hard way for the DoFill() method, simply as a counterpoint to the easy method shown for the DoDataUpdate() method. Either method of working with the recordset is viable and completely correct, but the first method we'll discuss is more time consuming to implement— something you should consider if you don't require the additional flexibility that it provides. In short, always look for the easy ADO method for accomplishing any given task.

The DoFill() method begins by retrieving the data using the GetRows() method. We only need to retrieve one record at a time, but the GetRows() method enables you to retrieve any number of records. The advantage of using this technique is that you could build in-memory databases of static data using arrays. Notice that you must reposition the record pointer after using GetRows() because this call always positions the record pointer at the next record.

We also need to perform an odd data transition when using the GetRows() method because it will only return an Object, not an Object Array. You can view the returned data in the Retrieved object using the debugger, but you can't access the individual array elements until you convert the Object to an Object Array. Of course, you can't perform this transition directly, so you need to use the Array.Copy() method to accomplish the task. Once the code performs the conversion, placing the resulting data in the form fields is easy.

As previously mentioned, the DoDataUpdate() method shows the easy way of working with DBRecordset. Here's the problem with the second method—it isn't documented. You can look in the help file (or even the MSDN subscription for that matter) and you'll never find the set_Collect() method. Unless you spend time working with the ADODB namespace in Object Explorer, you'll never find gems like this undocumented method and you'll face endless hours of misery developing an application the hard way. The set_Collect() method sets the data, while the get_Collect() method gets the data for any accessible field in the current record. Notice that the DoDataUpdate() method lacks a call to the DBRecordset.Update() method. You'll find that you don't need to make the call to update the database and that it won't work, in most cases, if you do.

The DoAdd() method relies on two arrays to add a record to DBRecordset. The first array contains a list of field numbers (don't try to use field strings—they won't work in some cases), while the second array contains default values for those fields. You must assign a value to every field in the new record except for automatically numbered fields (such as InventoryID in the example).

After you add the new record, you must use Requery() to fill DBRecordset with the actual new record from the database. If you don't query the database, DBRecordset will only contain the default values provided as part of the AddNew() call and the DoFill() method will fail with a null value error. Unfortunately, using the Requery() method positions the record pointer to the first record in DBRecordset, so we need to reposition the record pointer to the new record (the last record in every case for the example). The last step of adding the new record is to display it on screen. Notice that the DoAdd() method lacks any code for updating the new record with user input from the form—the application takes care of this automatically as part of the update process.

It's interesting to note that ADO makes it easier to delete an existing record than to add a new one. The DoDelete() method uses the DBRecordset.Delete() method to delete the current record, and then it simply displays the first record of the database on screen after a Requery() call. You can delete something other than the current record, so it's important to add a value from the AffectEnum enumeration.

Moving Between Records

One of the best features of using ADO is that it makes moving between records simple. All you need to do is use one of a number of Move() methods to change the record pointer position. From this perspective alone, using ADO is a lot easier than using OLE-DB. Listing 12.3 shows the code you'll need to move from record-to-record in this example.

Listing 12.3: ADO Makes Moving Between Records Easy

private void DoFirst()

{

//Update the data. DoDataUpdate();

//Move to the first record. DBRecordset.MoveFirst();

//Update the screen. DoFill();

}

private void DoNext()

{

//Update the data. DoDataUpdate();

//Move to the next record. DBRecordset.MoveNext();

//If we are at the end of the database,

//move back one and display a message. if (DBRecordset.EOF)

{

DBRecordset.MovePrevious(); MessageBox.Show("Already at last record!",

"Dataset Pointer", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

// Update the screen. DoFill();

}

private void DoPrevious()

{

//Update the data. DoDataUpdate();

//Move to the previous record. DBRecordset.MovePrevious();

//If we are at the beginning of the database,

//move forward one and display a message.

if (DBRecordset.BOF)

{

DBRecordset.MoveFirst(); MessageBox.Show("Already at first record!",

"Dataset Pointer", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

}

// Update the screen. DoFill();

}

private void DoLast()

{

//Update the data. DoDataUpdate();

//Move to the last record. DBRecordset.MoveLast();

//Update the screen. DoFill();

}

As you can see, all four of the record movement methods share three elements in common. First, they update the data so that any changes the user makes will appear in the current record before the code changes to the next record. Second, the code uses one of four record movement commands: MoveFirst(), MoveNext(), MovePrevious(), or MoveLast() to change the record pointer. Third, the code calls DoFill() to show the changed record pointer position on screen.

The DoNext() and DoPrevious() methods require additional code to ensure the user doesn't attempt to move past the end or the beginning of the recordset. Two special properties, EOF

(end of file) and BOF (beginning of file) tell the application when the record pointer is pointing to a special phantom record that marks the beginning and end of the recordset. All DBMS have a BOF and EOF concept, but ADO makes it easy to determine when this event occurs. It's educational to compare the implementation of an ADO record movement method to the OLE-DB equivalent because OLE-DB lacks any means of checking for either EOF or BOF.

Now that we've discussed the important differences in the user interface and recordset manipulation code for ADO, it's time to see what the application looks like in operation. Figure 12.5 shows the output of this application. After looking at the amount of data displayed for each record, it's easy to understand why this example relies on a Detail view, rather than a Grid view. The Grid view would hide important details that the user would need to know in order to make any decisions about the movie. Of course, a Grid view add-on would make it easier to perform movie searches based on specific criteria.

Figure 12.5: The ADO Demonstration application uses an efficient Detail view for data access.

Printing the Data

Most of the print routines for any given application are the same no matter what type of database access technology you use, so we won't look at everything required for printing in this chapter because we've already visited the topic in detail in Chapter 11. (In fact, I cut and paste the routines from Chapter 11 into the example for this chapter in order to demonstrate there are few differences you need to consider.) However, the one item that will always change is the PrintPageEventHandler(). The data access technology you choose for your application will have a very big impact on the method assigned to handle print page events. Listing 12.4 shows the event handler for this example.

Listing 12.4: The PrintPageEventHandler() is the Core of Every Print Routine in .NET

void PD_PrintPage(Object sender, PrintPageEventArgs ev)

{

Font

docFont;

// Document Font

Font

headFont;

// Heading Font

Font

columnFont;

// Column Font

float

yPos = 20;

// Position of text on page.

DateTime

PurchaseDate;

// Purchase date of the film.

string

IsItIn;

// Is the movie in stock?

// Create the font.

docFont = new Font("Arial", 11);

headFont = new Font("Arial", 24);

columnFont = new Font("Arial", 12, FontStyle.Bold);

// Print the heading. ev.Graphics.DrawString("Movies 'R Us Movie Listing",

headFont,

Brushes.Black,

20,

yPos);

yPos = yPos + headFont.GetHeight() + 20;

// Print the column headings. ev.Graphics.DrawString("Name",

columnFont,

Brushes.Black,

20,

yPos);

ev.Graphics.DrawString("Rating",

columnFont,

Brushes.Black,

240,

yPos);

ev.Graphics.DrawString("Format",

columnFont,

Brushes.Black,

320,

yPos); ev.Graphics.DrawString("Purchase Date",

columnFont,

Brushes.Black,

420,

yPos); ev.Graphics.DrawString("Is It In?",

columnFont,

Brushes.Black,

580,

yPos);

yPos = yPos + columnFont.GetHeight() + 15;

//Go to the first record in the recordset. DBRecordset.MoveFirst();

//Continue printing as long as there is space on the page and

//we don't run out of things to write.

while (!DBRecordset.EOF)

{

// Print the line of text. ev.Graphics.DrawString(DBRecordset.get_Collect("Name").ToString(),

docFont,

Brushes.Black,

20,

yPos); ev.Graphics.DrawString(DBRecordset.get_Collect("Rating").ToString(),

docFont,

Brushes.Black,

240,

yPos); ev.Graphics.DrawString(DBRecordset.get_Collect("Format").ToString(),

docFont,

Brushes.Black,

320,

yPos);

PurchaseDate =

DateTime.Parse(DBRecordset.get_Collect("DatePurchased").ToString()); ev.Graphics.DrawString(PurchaseDate.ToShortDateString(),

docFont,

Brushes.Black,

420,

yPos);

if ((bool)DBRecordset.get_Collect("IsIn")) IsItIn = "Yes";

else

IsItIn = "No"; ev.Graphics.DrawString(IsItIn,

docFont,

Brushes.Black,

580,

yPos);

//Determine the next print position. yPos = yPos + docFont.GetHeight() + 10;

//Move to the next record. DBRecordset.MoveNext();

}

//Tell the application there are no more pages to print. ev.HasMorePages = false;

//Make sure we're at a valid record. DBRecordset.MoveFirst();

DoFill();

}

There are similarities between the code in this chapter and the code in Chapter 11. For example, you still need to print headers for the report and provide other static functionality that doesn't change much in concept from application to application. Once you get past these obvious similarities, however, everything changes.

The code begins by moving to the first record position in DBRecordset using the MoveFirst() method. Unlike the data grid used in Chapter 11, DBRecordset only points to one record at a time, so we need to ensure the record pointer is in the right position.

Unlike the print routine in Chapter 11, you don't need to know how many records DBRecordset contains before you begin printing detail rows. DBRecordset includes the EOF property you can use to validate the end of the recordset. The detail row routine will continue printing pages until it runs out of records. Of course, the example shows a simple implementation that you can expand to meet specific needs.

Notice that the print routine uses the same DrawString() method found in Chapter 11 for displaying detail rows. However, the method for accessing the data differs. In this case, the code uses the simple method of accessing the individual field values for the current record—

the get_Collect() method. As you can see, all you need to provide is the name of the field of interest.

The single record orientation of ADO comes into play again near the end of the routine. First, you need to use the MoveNext() method to ensure the recordset always points to the next record. Second, once the print routine completes, the code uses MoveFirst() method to position the record pointer to a valid record. Remember that the print routine continues until the EOF condition is true. This means DBRecordset would point to an invalid record unless you move the recordset at the end of the routine.

In all other ways, this example works the same as the example in Chapter 11. When you run the Print Preview code, you'll see a movie listing similar to the one shown in Figure 12.6.

Figure 12.6: The Print Preview view of the data contains a listing of the facts most users will need to locate a specific title.

Troubleshooting Techniques for ADO Applications

Most developers find that ADO is easier to use than OLE-DB, even though it does hide some of the database transaction details (or perhaps because it hides the details). Of course, the ease of using ADO doesn't means you'll have an error-free environment in which to work. While errors are less common when using ADO because the underlying components take care of most of the work for you, finding an error can be more difficult because you can't see what's going on in many cases. The following sections discuss common problems you'll encounter as you create ADO applications using C#.

General

One of the most general rules to remember about ADO is that Microsoft provides multiple ways of performing any given task. However, as we saw in the "Understanding Data Manipulation" section of the chapter, one method is normally easier to use than any of the other available methods. Consequently, if you want to reduce development time, it pays to look for the easy way to accomplish a task. While the Recordset object doesn't provide enumeration, it usually provides some means of gaining access to individual data values, such as the get_Collect() method used for printing in this example.

Unlike other data access methods we discussed in Chapter 11, ADO does provide a means for detecting the EOF and BOF conditions. In some respects, this makes the job of writing code much easier. You don't have to use the weird end-of-data methods we used in Chapter 11 to move to the last record in the dataset. On other hand, ADO is also susceptible to EOF and BOF error conditions. Code can't manipulate the phantom records at the beginning or end of the database. Therefore, if you're having an odd, poorly defined error crop up during a data manipulation, check for the EOF or BOF condition as part of your troubleshooting.

Method Calls

One of the oddities of working with ADO is that it uses COM objects and the .NET Framework doesn't always provide the best translation of COM object methods. You'll notice that all of the method calls in this chapter include entries for every possible argument, even though many arguments are optional. The reason for including all arguments is that the application won't compile without them.

Of course, the requirement to provide an entry for every argument means you can't assume any default values when making a method call. One way around this problem is to provide null values for optional arguments. This technique works in many cases, but not in others. If you find that your code is failing for some "unknown" reason, it's probable that you've used a null value where an actual argument is required.

Another problem with method call arguments is that you'll often need to provide a value of type Variant. Unfortunately, this type isn't available in .NET, so you need to substitute the Object type. Always assume that you must provide an Object, not a string or some other value type, when working with any argument that includes an optional Variant argument. For example, the Recordset object GetRows() method can accept either a string or a Variant in the second argument. The only type that will work under .NET is the Variant (Object) type. Likewise, when the documentation states that you can provide a string, numeric value, or an array of either a string or a numeric value for the third argument for the GetRows() method call, what you really need to do is provide an Object. Nothing else will work, in this case, and the error message you receive from ADO is confusing at best.

ADO and the Garbage Collector

It's important to remember that ADO is a COM technology. This fact means that CLR can't release resources used by ADO automatically—you need to remember to release them. In addition, you'll need to use some bizarre techniques at times to ensure the accessibility of application data to ADO. This means pinning values when you run into problems with ADO access to the memory (the Garbage Collector releases the resource before ADO can use it).

Let's discuss one of the most important tasks you can perform when it comes to ADO and that Garbage Collector. You'll need to close the handles to the ADO objects and then set the objects to a null reference before the application closes. Otherwise, the application will leak memory and other resources—the Garbage Collector is unable to help with this particular chore. Here's an example of the code you'll use.

private void MainForm_Closing(object sender, System.ComponentModel.CancelEventArgs e)

{

// Close the recordset and the connection.

DBRecordset.Close();

DBConnect.Close();

// Set the objects up for deletion. DBRecordset = null;

DBConnect = null;

}

As you can see, there's nothing mysterious about this code, but you need to include it with your application. Notice that the code appears as a form Closing() event handler. You can theoretically place this code in other areas, but releasing global variables in the MainForm_Closing() method seems to work best.

You might be tempted to close and release the various ADO objects after each use, but this would incur a huge performance penalty in your application. The forte of ADO is the live database connection. In addition, you'd have to reposition the record pointer every time the user decided to perform any task. Tracking the current record will prove troublesome to say the least. However, it's a good idea to release local resources as soon as you finish using them. For example, an intermediate data result is a good candidate for immediate release.

Where Do You Go From Here?

This chapter has shown you how to work with Server Explorer, one of the more important developer productivity aids in the Visual Studio .NET IDE. You've learned how to use Server Explorer to gain access to a variety of data types and how to use it to build your own databases. We've also discussed ADO, an important database technology in that it enables you to create quick connections to databases with less work than using OLE-DB. Of course, ADO doesn't provide the level of automation that OLE-DB does in C#.

The Server Explorer is complex enough that you won't learn about everything it can provide in one or two sessions. Yet, this tool is an essential productivity aid, so learning everything it can do is important. One of the ways to develop a better appreciation of the Server Explorer is to spend time developing small projects with it. Of course, you'll want to create projects that could yield usable code for larger applications later.

Combining Server Explorer with database technologies such as ADO makes a simple tool more powerful. We used this approach in the performance counter example in the chapter, but it's useful in many other projects. Spend the time required to learn how to manage the data exposed by Server Explorer with datasets and how to store the resulting data in databases for future use.

Chapter 13 will show you how to use the latest Microsoft database technology, ADO.NET. This new technology combines ease-of-use, fast development time, and a high-level view of the data management process in a managed application package. As you'll learn, ADO.NET does have limitations that you'll need to consider when designing your next database application, but the technology does represent a giant leap forward for the developer.

Chapter 13: ADO.NET Application

Development