2. When the app executes, another compiler (known as the just-in-time compiler
22.9 Address Book Case Study
22.9 Address Book Case Study
Our final example (Fig. 22.32) implements a simpleAddressBookapp that enables users to perform the following tasks on the databaseAddressBook.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.
Rather than displaying a database table in a DataGridView, this app presents the details of one contact at a time in severalTextBoxes. TheBindingNavigatorat the top of the window allows you to control whichrowof the table is displayed at any given time.
TheBindingNavigatoralso allows you toadda contact anddeletea contact—but only 21 // initialize data sources when the Form is loaded
22 private void Details_Load( object sender, EventArgs e )
23 {
24 // load Authors table ordered by LastName then FirstName 25
26 27 28 29
30 // specify DataSource for authorBindingSource 31
32 } // end method Details_Load 33 } // end class Details
34 } // end namespace MasterDetail
Fig. 22.32 | Manipulating an address book. (Part 1 of 2.)
Fig. 22.31 | Using aDataGridViewto display details based on a selection. (Part 2 of 2.)
dbcontext.Authors
.OrderBy( author => author.LastName ) .ThenBy( author => author.FirstName ) .Load();
authorBindingSource.DataSource = dbcontext.Authors.Local;
a) Use theBindingNavigator’s controls to navigate through the contacts in the database
when browsing thecompletecontact list. When you filter the contacts by last name, the app disables theAdd new( ) andDelete( ) buttons (we’ll explain why shortly). Clicking
Browse All Entriesenables these buttons again. Adding a row clears theTextBoxes and sets theTextBoxto the right ofAddress IDto zero to indicate that theTextBoxes now represent a new record. When you save a new entry, theAddress IDfield is automatically changed from zero to a unique ID number by the database. No changes are made to the underlying database unless you click theSave Data( ) button.
22.9.1 Creating theAddress BookApp’s GUI
We discuss the app’s code momentarily. First you’ll set up the entity data model and a WindowsForms app.
Step 1: Creating aClass LibraryProject for the Entity Data Model
Perform the steps in Section 22.5.1 to create aClass Libraryproject namedAddressExam- plethat contains an entity data model for theAddressBook.mdfdatabase, which contains only anAddressestable withAddressID,FirstName,LastName,EmailandPhoneNumber
columns. Name the entity data modelAddressModel.edmx. TheAddressBook.mdfdata- base is located in theDatabasesfolder with this chapter’s examples.
Fig. 22.32 | Manipulating an address book. (Part 2 of 2.)
b) Type a searchstringin the Last Name:TextBoxthen press Findto locate contacts whose last names begin with thatstring; only two names start with “Br” so theBindingNavigatorindicates two matching records
Displaying the first of two matching contacts for the current search
c) ClickBrowse All Entries to clear the searchstring and allow browsing of all contacts in the database
You can now browse through all six contacts
22.9 Address Book Case Study 885
Step 2: Creating aWindows Forms ApplicationProject forAddressBookApp Perform the steps in Section 22.5.2 to create a newWindows Forms Applicationproject named AddressBook in the AddressExample solution. Set theForm’s filename toCon-
tacts.cs, then set theForm’sTextproperty toAddress Book. Set theAddressBookproject as the solution’s startup project.
Step 3: Adding theAddressObject as a Data Source
Add the entity data model’sAddressobject as a data source, as you did with theAuthor object inStep 1of Section 22.5.3.
Step 4: Displaying the Details of Each Row
InDesignview, select theAddressnode in theData Sourceswindow. Click theAddress node’s down arrow and select theDetailsoption to indicate that the IDE should create a set ofLabel–TextBoxpairs to show the details of a single record at a time.
Step 5: Dragging theAddressData-Source Node to theForm
Drag theAddressnode from the Data Sourceswindow to theForm. This automatically creates aBindingNavigatorand theLabels andTextBoxes corresponding to the columns of the database table. The fields are placed in alphabetical order. Reorder the components, usingDesignview, so they’re in the order shown in Fig. 22.32. You’ll also want to change the tab order of the controls. To do so, selectVIEW > Tab Orderthen click theTextBoxes from top to bottom in the order they appear in Fig. 22.32.
Step 5: Making theAddressID TextBox ReadOnly
TheAddressIDcolumn of theAddressestable is anautoincremented identity column, so users shouldnotbe allowed to edit the values in this column. Select theTextBoxfor the
AddressIDand set itsReadOnlyproperty toTrueusing thePropertieswindow.
Step 6: Adding Controls to Allow Users to Specify a Last Name to Locate
While theBindingNavigatorallows 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 han- dlers to perform the search.
Add aLabelnamedfindLabel, aTextBoxnamedfindTextBox, and aButtonnamed
findButton. Place these controls in aGroupBoxnamedfindGroupBox, then set itsText property toFind an entry by last name. Set theTextproperty of theLabeltoLast Name:
and set theTextproperty of theButtontoFind.
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 spe- cific last name, add aButtonnamedbrowseAllButtonbelow thefindGroupBox. Set the
Textproperty ofbrowseAllButtontoBrowse All Entries. 22.9.2 Coding theAddress BookApp
TheContacts.cscode-behind file is split into several figures (Figs. 22.33–22.37) for pre- sentation purposes.
MethodRefreshContacts
As we showed in previous examples, we must connect theaddressBindingSourcethat controls the GUI with theDbContextthat interacts with the database. In this example, we declare theAddressEntities DbContextobject at line 20 of Fig. 22.33, but create it and initiate the data binding in theRefreshContactsmethod (lines 23–43), which is called from several other methods in the app. When this method is called, ifdbcontextis not
null, we call itsDisposemethod, then create anew AddressEntities DbContextat line 30. We do this so we can re-sort the data in the entity data model. If we maintained one
dbcontext.Addressesobject 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 thedbcontext.Addressesobject, even if that order is incorrect. Lines 34–37 or- der theAddressobjects byLastName, thenFirstNameand load the objects into memory.
Then line 40 sets theaddressBindingSource’sDataSourceproperty todbcontext.Ad- dresses.Localto bind the data in memory to the GUI.
1 // Fig. 22.33: 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 } // end constructor 18
19 // Entity Framework DbContext 20
21
22 // fill our addressBindingSource with all rows, ordered by name 23 private void RefreshContacts()
24 {
25 // Dispose old DbContext, if any 26
27 28
29 // create new DbContext so we can reorder records based on edits 30
31
32 // use LINQ to order the Addresses table contents 33 // by last name, then first name
Fig. 22.33 | Creating theBooksDataContextand defining methodRefreshContactsfor use in other methods. (Part 1 of 2.)
private AddressExample.AddressBookEntities dbcontext = null;
if ( dbcontext != null ) dbcontext.Dispose();
dbcontext = new AddressExample.AddressBookEntities();
22.9 Address Book Case Study 887
MethodContacts_Load
MethodContacts_Load(Fig. 22.34) callsRefreshContacts(line 48) so that the first re- cord is displayed when the app starts. As before, you create theLoadevent handler by dou- ble clicking theForm’s title bar.
MethodaddressBindingNavigatorSaveItem_Click
MethodaddressBindingNavigatorSaveItem_Click(Fig. 22.35) saves the changes to the database when theBindingNavigator’sSave DataButtonis clicked. (Remember to enable this 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, aDbEntityVal- idationExceptionexception occurs. We callRefreshContactsafter saving to re-sort the data and move back to the first element.
34 35 36 37 38
39 // specify DataSource for addressBindingSource 40
41 addressBindingSource.MoveFirst(); // go to first result 42 findTextBox.Clear(); // clear the Find TextBox
43 } // end method RefreshContacts 44
45 // when the form loads, fill it with data from the database 46 private void Contacts_Load( object sender, EventArgs e )
47 {
48
49 } // end method Contacts_Load 50
Fig. 22.34 | CallingRefreshContactsto fill theTextBoxes when the app loads.
51 // Click event handler for the Save Button in the 52 // BindingNavigator saves the changes made to the data 53 private void addressBindingNavigatorSaveItem_Click(
54 object sender, EventArgs e )
55 {
56 Validate(); // validate input fields
57 addressBindingSource.EndEdit(); // complete current edit, if any 58
59 // try to save changes
60 try
61 {
Fig. 22.35 | Saving changes to the database when the user clicksSave Data. (Part 1 of 2.) Fig. 22.33 | Creating theBooksDataContextand defining methodRefreshContactsfor use in other methods. (Part 2 of 2.)
dbcontext.Addresses
.OrderBy( entry => entry.LastName ) .ThenBy( entry => entry.FirstName ) .Load();
addressBindingSource.DataSource = dbcontext.Addresses.Local;
RefreshContacts(); // fill binding with data from database
MethodfindButton_Click
MethodfindButton_Click(Fig. 22.36) uses LINQ query syntax (lines 79–83) to select only people whose last names start with the characters in thefindTextBox. The query sorts the results by last name then first name. In LINQ to Entities, youcannotbind a LINQ que- ry’s results directly to aBindingSource’sDataSource. So, line 86 calls the query object’s
ToListmethod to get aListrepresentation of the filtered data and assigns theListto the
BindingSource’sDataSource. When you convert the query result to aList, only changes toexistingrecords in theDbContextare tracked by theDbContext—any records that you add or remove while viewing the filtered data would be lost. For this reason we disabled the
Add newandDeletebuttons when the data is filtered. When you enter a last name and click
Find, theBindingNavigatorallows the user to browse only the rows containing the match- ing last names. This is because the data source bound to theForm’s controls (the result of the LINQ query) has changed and now contains only a limited number of rows.
62 dbcontext.SaveChanges(); // write changes to database file
63 } // end try
64 catch ( DbEntityValidationException )
65 {
66 MessageBox.Show( "Columns cannot be empty",
67 "Entity Validation Exception" );
68 } // end catch
69 70
71 } // end method addressBindingNavigatorSaveItem_Click 72
73 // use LINQ to create a data source that contains only people 74 // with last names that start with the specified text
75 private void findButton_Click( object sender, EventArgs e )
76 {
77 // use LINQ to filter contacts with last names that 78 // start with findTextBox contents
79 80 81 82 83 84
85 // display matching contacts 86
87 addressBindingSource.MoveFirst(); // go to first result 88
89 // don't allow add/delete when contacts are filtered 90 bindingNavigatorAddNewItem.Enabled = false;
91 bindingNavigatorDeleteItem.Enabled = false;
92 } // end method findButton_Click 93
Fig. 22.36 | Finding the contacts whose last names begin with a specifiedString. Fig. 22.35 | Saving changes to the database when the user clicksSave Data. (Part 2 of 2.)
RefreshContacts(); // change back to initial unfiltered data
var lastNameQuery =
from address in dbcontext.Addresses
where address.LastName.StartsWith( findTextBox.Text ) orderby address.LastName, address.FirstName
select address;
addressBindingSource.DataSource = lastNameQuery.ToList();