22.9 Address Book Case Study

Our final example implements a simple AddressBook app (with sample outputs in (Figs. 22.3822.40) that enables users to perform the following tasks on the database AddressBook.mdf (which is included in the directory with this chapter’s examples):

  • Insert new contacts.

  • Find contacts whose last names begin with the specified letters.

  • Update existing contacts.

  • Delete contacts.

We populated the database with six fictional contacts.

Fig. 22.38 Use the BindingNavigator’s controls to navigate through the contacts.

Fig. 22.39 Type a search string in the Last Name TextBox, then press Find to locate contacts whose last names begin with that string. Only two names start with Br, so the BindingNavigator shows the first of two matching records.

Fig. 22.40 Click Browse All Entries to clear a search and return to browsing all contacts.

Rather than displaying a database table in a DataGridView, this app presents the details of one contact at a time in several TextBoxes. The BindingNavigator at the top of the window allows you to control which row of the table is displayed at any given time. The BindingNavigator also allows you to add a contact and delete a contact—but only when browsing the complete contact list. When you filter the contacts by last name, the app disables the Add new

and Delete

buttons (we’ll explain why shortly). Clicking Browse All Entries enables these buttons again. Adding a row clears the TextBoxes and sets the TextBox to the right of Address ID to zero to indicate that the TextBoxes now represent a new record. When you save a new entry, the Address ID field is automatically changed from zero to a unique ID number by the database. No changes are made to the underlying database unless you click the Save Data button:

22.9.1 Creating the Address Book App’s GUI

We discuss the app’s code momentarily. First you’ll set up a new solution containing the entity data model and a Windows Forms app. Close the BooksExamples solution you used in this chapter’s previous examples.

Step 1: Creating a Class Library Project for the Entity Data Model

Perform the steps in Section 22.5.1 to create a Class Library project named AddressExample that contains an entity data model for the AddressBook.mdf database, which contains only an Addresses table with AddressID, FirstName, LastName, Email and PhoneNumber columns. Name the entity data model AddressModel. The AddressBook.mdf database is located in the Databases folder with this chapter’s examples.

Step 2: Creating a Windows Forms Application Project for the AddressBook App

Perform the steps in Section 22.5.2 to create a new Windows Forms Application project named AddressBook in the AddressExample solution. Set the Form’s filename to Contacts.cs, then set the Form’s Text property to Address Book. Set the AddressBook project as the solution’s startup project.

Step 3: Adding the Address Object as a Data Source

Add the entity data model’s Address object as a data source, as you did with the Author object in Step 1 of Section 22.5.3.

Step 4: Displaying the Details of Each Row

In Design view, select the Address node in the Data Sources window. Click the Address node’s down arrow and select the Details option to indicate that the IDE should create a set of LabelTextBox pairs to show the details of a single record at a time.

Step 5: Dragging the Address Data-Source Node to the Form

Drag the Address node from the Data Sources window to the Form. This automatically creates a BindingNavigator and the Labels and TextBoxes corresponding to the columns of the database table. The fields are placed in alphabetical order. Reorder the components, using Design view, so they’re in the order shown in Fig. 22.38. You’ll also want to change the tab order of the controls. To do so, select View > Tab Order, then click the TextBox es from top to bottom in the order they appear in Fig. 22.38.

Step 5: Making the AddressID TextBox ReadOnly

The AddressID column of the Addresses table is an autoincremented identity column, so users should not be allowed to edit the values in this column. Select the TextBox for the AddressID and set its ReadOnly property to True using the Properties window.

Step 6: Adding Controls to Allow Users to Specify a Last Name to Locate

While the BindingNavigator allows you to browse the address book, it would be more convenient to be able to find a specific entry by last name. To add this functionality to the app, we must create controls to allow the user to enter a last name and provide event handlers to perform the search.

Add a Label named findLabel, a TextBox named findTextBox, and a Button named findButton. Place these controls in a GroupBox named findGroupBox, then set its Text property to Find an entry by last name. Set the Text property of the Label to Last Name: and set the Text property of the Button to Find.

Step 7: Allowing the User to Return to Browsing All Rows of the Database

To allow users to return to browsing all the contacts after searching for contacts with a specific last name, add a Button named browseAllButton below the findGroupBox. Set the Text property of browseAllButton to Browse All Entries.

22.9.2 Coding the Address Book App

The Contacts.cs code-behind file is split into several figures (Figs. 22.4122.45) for presentation purposes.

Method RefreshContacts

As we showed in previous examples, we must connect the addressBindingSource that controls the GUI with the DbContext that interacts with the database. In this example, we declare the AddressEntities DbContext object at line 20 of Fig. 22.41, but create it and initiate the data binding in the RefreshContacts method (lines 23–45), which is called from several other methods in the app. When this method is called, if dbcontext is not null, we call its Dispose method, then create a new AddressEntities DbContext at line 32. We do this so we can re-sort the data in the entity data model. If we maintained one dbcontext.Addresses object in memory for the duration of the program and the user changed a person’s last name or first name, the records would still remain in their original order in the dbcontext.Addresses object, even if that order is incorrect. Lines 36–39 order the Address objects by LastName, then FirstName and load the objects into memory. Then line 42 sets the addressBindingSource’s DataSource property to dbcontext.Addresses.Local to bind the data in memory to the GUI.

Fig. 22.41 Creating the BooksEntities and defining method RefreshContacts for use in other methods.

Alternate View

   1    // Fig. 22.41: Contact.cs
   2    // Manipulating an address book.
   3    using System;
   4    using System.Data;
   5    using System.Data.Entity;
   6    using System.Data.Entity.Validation;
   7    using System.Linq;
   8    using System.Windows.Forms;
   9
  10    namespace AddressBook
  11    {
  12       public partial class Contacts : Form
  13       {
  14          public Contacts()
  15          {
  16             InitializeComponent();
  17          }
  18
  19          // Entity Framework DbContext
  20          private AddressExample.AddressBookEntities dbcontext = null;
  21
  22          // fill our addressBindingSource with all rows, ordered by name
  23          private void RefreshContacts()
  24          {
  25             // Dispose old DbContext, if any
  26             if (dbcontext != null)
  27             {
  28                 dbcontext.Dispose();
  29             }
  30
  31             // create new DbContext so we can reorder records based on edits
  32             dbcontext = new AddressExample.AddressBookEntities();
  33
  34             // use LINQ to order the Addresses table contents
  35             // by last name, then first name
  36             dbcontext.Addresses                 
  37                .OrderBy(entry => entry.LastName)
  38                .ThenBy(entry => entry.FirstName)
  39                .Load();                         
  40
  41             // specify DataSource for addressBindingSource
  42             addressBindingSource.DataSource = dbcontext.Addresses.Local;
  43             addressBindingSource.MoveFirst(); // go to first result     
  44             findTextBox.Clear(); // clear the Find TextBox
  45          }
  46

Method Contacts_Load

Method Contacts_Load (Fig. 22.42) calls RefreshContacts (line 50) so that the first record is displayed when the app starts. As before, you create the Load event handler by double clicking the Form’s title bar.

Fig. 22.42 Calling RefreshContacts to fill the TextBoxes when the app loads.

Alternate View

  47             // when the form loads, fill it with data from the database
  48             private void Contacts_Load(object sender, EventArgs e)
  49             {
  50                RefreshContacts(); // fill binding with data from database
  51             }
  52

Method addressBindingNavigatorSaveItem_Click

Method addressBindingNavigatorSaveItem_Click (Fig. 22.43) saves the changes to the database when the BindingNavigator’s Save Data Button is clicked. (Remember to enablethis button.) The AddressBook database requires values for the first name, last name, phone number and e-mail. If a field is empty when you attempt to save, a DbEntityValidationException exception occurs. We call RefreshContacts (line 72) after saving to re-sort the data and move back to the first element.

Fig. 22.43 Saving changes to the database when the user clicks Save Data.

Alternate View

  53             // Click event handler for the Save Button in the
  54             // BindingNavigator saves the changes made to the data
  55             private void addressBindingNavigatorSaveItem_Click(
  56                object sender, EventArgs e)
  57             {
  58                Validate(); // validate input fields
  59                addressBindingSource.EndEdit(); // complete current edit, if any
  60
  61                // try to save changes
  62                try
  63                {
  64                  dbcontext.SaveChanges(); // write changes to database file
  65                 }
  66                 catch (DbEntityValidationException)
  67                 {
  68                    MessageBox.Show("Columns cannot be empty",
  69                      "Entity Validation Exception");
  70             }
  71
  72             RefreshContacts(); // change back to updated unfiltered data
  73          }
  74

Method findButton_Click

Method findButton_Click (Fig. 22.44) uses LINQ query syntax (lines 81–85) to select only people whose last names start with the characters in the findTextBox. The query sorts the results by last name, then first name. In LINQ to Entities, you cannot bind a LINQ query’s results directly to a BindingSource’s DataSource. So, line 88 calls the query object’s ToList method to get a List representation of the filtered data and assigns the List to the BindingSource’s DataSource. When you convert the query result to a List, only changes to existing records in the DbContext are tracked by the DbContext—any records that you add or remove while viewing the filtered data would be lost. For this reason we disabled the Add new and Delete buttons when the data is filtered. When you enter a last name and click Find, the BindingNavigator allows the user to browse only the rows containing the matching last names. This is because the data source bound to the Form’s controls (the result of the LINQ query) has changed and now contains only a limited number of rows.

Fig. 22.44 Finding the contacts whose last names begin with a specified String.

Alternate View

  75              // use LINQ to create a data source that contains only people
  76              // with last names that start with the specified text
  77              private void findButton_Click(object sender, EventArgs e)
  78              {
  79                 // use LINQ to filter contacts with last names that
  80                 // start with findTextBox contents
  81                 var lastNameQuery =                                   
  82                    from address in dbcontext.Addresses                
  83                    where address.LastName.StartsWith(findTextBox.Text)
  84                    orderby address.LastName, address.FirstName        
  85                    select address;                                    
  86
  87                  // display matching contacts
  88                  addressBindingSource.DataSource = lastNameQuery.ToList();
  89                  addressBindingSource.MoveFirst(); // go to first result  
  90
  91                  // don't allow add/delete when contacts are filtered
  92                  bindingNavigatorAddNewItem.Enabled = false;
  93                  bindingNavigatorDeleteItem.Enabled = false;
  94              }
  95

Method browseAllButton_Click

Method browseAllButton_Click (Fig. 22.45) allows users to return to browsing all the rows after searching for specific rows. Double click browseAllButton to create a Click event handler. The event handler enables the Add new and Delete buttons, then calls RefreshContacts to restore the data source to the full list of people (in sorted order) and clear the findTextBox.

Fig. 22.45 Allowing the user to browse all contacts.

Alternate View

  96              // reload addressBindingSource with all rows
  97              private void browseAllButton_Click(object sender, EventArgs e)
  98              {
  99                 // allow add/delete when contacts are not filtered
 100                bindingNavigatorAddNewItem.Enabled = true;
 101                bindingNavigatorDeleteItem.Enabled = true;
 102                RefreshContacts(); // change back to initial unfiltered data
 103             }
 104        }
 105 }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset