I have seen estimates claiming that 80% or more of all Visual Basic applications involve a database. I've even seen one estimate that claims 95% of all Visual Basic applications involve a database at some point. Whatever the true number is, it's clear that a large fraction of Visual Basic programs use databases.
There are some good reasons for this. Databases provide a lot of useful features for storing, manipulating, updating, and retrieving data. A well-designed database can store huge amounts of data and still find a particular record quickly.
With such a strong emphasis on databases, it's small wonder that Microsoft spends a lot of time and effort providing tools to manipulate databases, and over the years Microsoft has developed many such tools. The latest of these is the ADO.NET Entity Framework.
In this lesson, you learn the basics of using the Entity Framework. You learn how to use it to map data in a database to objects in a program. You also learn how to build a simple program that uses the objects created by the Entity Framework to manipulate the data in the database.
Although a detailed discussion of the Entity Framework is beyond the scope of this book, this section explains its general concepts and key features.
In brief, the Entity Framework is an object-relational mapper (abbreviated ORM, O/RM, or O/R mapper), a tool that maps between pieces of data in a relational database and objects in a program. That mapping enables you to program using objects such as Person, Customer
, and Order
without worrying about how those objects are stored in the database; and the ORM automatically loads and saves data as needed.
For example, suppose an Order
object defines an OrderedBy
property that refers to the Customer
who placed an order. When the program fetches an Order
object from the database, the ORM automatically loads the OrderedBy
property without the program needing to know how that happens.
Some of the more important features provided by the Entity Framework include the following:
Generating classes from a database — The Entity Framework can automatically generate classes to hold the data contained in some or all of a database's tables. It can even give classes properties that link tables together.
Generating classes from a model — After you design an entity data model, the designer automatically generates classes to hold the data in the model.
Model-first development — You can build a data model first and then use the Entity Framework to build an empty database for it.
Lazy loading — If an Order
object has a collection of OrderItems
, the Entity Framework Order
class can defer loading the OrderItems
until they are actually needed.
Following is one basic approach to using the Entity Framework:
Build a SQL Server database containing the data.
Use the Entity Framework to make a data model and generate classes.
Use the Entity Framework classes to manipulate data.
Rather than explain these steps in excruciating detail that won't make much sense to you until you actually try it yourself, the following sections walk you through these steps so you can see how they work. Basically, they form an extra-detailed walk-through similar to a Try It's Step-by-Step instructions.
To follow along you'll need the following:
Microsoft Visual Studio 2010 (which you should have if you're reading this book)
The .NET Framework 4 (which you should also have if you're reading this book)
Microsoft SQL Server 2005 or later, with Database Services or Microsoft SQL Server 2005 Express Edition or later
If you don't meet these requirements — in particular, if you don't have SQL Server installed — you can skim the rest of this lesson but you won't be able to work through the Try It or Exercises.
You may also want to install SQL Server Management Studio for use in building and manipulating databases. You can download SQL Server Management Studio Express from Microsoft's web site.
If you haven't installed SQL Server, you might want to do so now. The Visual Studio installation software comes with SQL Server (at least the versions I've seen) or you can download the free SQL Server Express edition at www.microsoft.com/express/Database
. You should probably download the management tools, too. You will find SQL Server Management Studio Express particularly useful for building databases.
If you need information about SQL Server and how to use it, there are many good books available, such as Robert Vieira's Beginning Microsoft SQL Server 2008 Programming (Wrox, 2009) and Professional Microsoft SQL Server 2008 Programming (Wrox, 2009).
You might also be interested in my book Beginning Database Design Solutions (Wrox, 2008), which explains how to design a database that is robust and flexible.
Assuming you have SQL Server installed, you need to create the database that your program will manipulate. To do that:
Start Microsoft SQL Server Management Studio Express.
When the program starts, it asks you to connect to the server that you will use. Enter the appropriate information and click Connect. Figure 39-1 shows the connect dialog that appears when I open my SQL Server Express server named Gothmog.
After you connect to the server, the Object Explorer should display the server's objects as shown in Figure 39-2. In this figure you can see the OrdersDatabase and its Customer, OrderItem, and PurchaseOrder tables.
You can use SQL Server Management Studio to build the database interactively. See the program's help for details.
Alternatively, you can execute a SQL script that contains commands to build the database for you. The file MakeOrdersDatabase.sql
, which is available in this lesson's download, builds a database called OrdersDatabase that contains Customer, PurchaseOrder, and OrderItem tables.
To run the script, download it from the book's web site. In SQL Server Management Studio, open the File menu, select Open, and pick File; or click [Ctrl]+O. Browse to the script and click Open. After you load the script, execute it by opening the Query menu and selecting Execute, or by pressing [f5].
The following code shows the parts of the script that create the database and its tables:
-- Create and use the OrdersDatabase. CREATE DATABASE OrdersDatabase; GO USE OrdersDatabase; -- Create the Customer table. CREATE TABLE Customer ( CustomerId int PRIMARY KEY, FirstName nvarchar(50) NOT NULL, LastName nvarchar(50) NOT NULL, ) -- Create the PurchaseOrder table. CREATE TABLE PurchaseOrder ( OrderId int PRIMARY KEY, OrderDate datetime NOT NULL, CustomerId int NOT NULL FOREIGN KEY REFERENCES Customer (CustomerId), ) -- Create the OrderItem table. CREATE TABLE OrderItem ( OrderId int NOT NULL FOREIGN KEY REFERENCES PurchaseOrder (OrderId), ItemNumber int NOT NULL, ItemDescription nvarchar(50) NOT NULL, Quantity int NOT NULL, CONSTRAINT PK_OrderItem PRIMARY KEY (OrderId, ItemNumber) )
You don't need to understand every detail of this script. You should be able to get the gist of it even if you're unfamiliar with the Transact-SQL database language that it uses.
The CREATE DATABASE
statement creates the OrdersDatabase inside the server.
The CREATE TABLE Customer
statement creates the Customer table and gives it the fields CustomerId, FirstName
, and LastName. CustomerId
is an integer, and the table's primary key. FirstName
and LastName
are strings with lengths up to 50. The NOT NULL
clause means all records must have these values filled in. (Primary keys must also always have values.)
The CREATE TABLE PurchaseOrder
statement similarly creates the PurchaseOrder table. The only new concept used by this statement is the FOREIGN KEY
clause, which indicates that this table's CustomerId
field references the Customer table's CustomerId
field. That means any record in the PurchaseOrder table must have a CustomerId
value that is already present in the Customer table. The idea is that a PurchaseOrder record's CustomerId
indicates the ID of the customer who placed the order.
Finally, the CREATE TABLE OrderItem
statement creates the OrderItem table. Its FOREIGN KEY
clause requires that its OrderId
field match a value in the PurchaseOrder table. The idea here is that an OrderItem record's OrderId
value indicates which PurchaseOrder includes the OrderItem.
This last CREATE TABLE
statement also uses a PRIMARY KEY
clause to indicate that the table's primary key includes the combination of OrderId
and ItemNumber
. Each order may contain several items, and their ItemNumber
values indicate the order in which they should appear in the purchase order.
You'll see a graphic representation of this database design in the next section.
This script tries to create the database without checking whether you already have a database named OrdersDatabase, so if one already exists it fails without damaging your existing database.
In addition to creating the OrdersDatabase and its Customer, PurchaseOrder, and OrderItem tables, the script inserts records in the tables to give your programs some data to manipulate. That code is long and not very interesting so it isn't shown here.
To make the data model, start a new Visual Basic Windows Forms project. Then open the Project menu and select Add New Item. On the Add New Item dialog, shown in Figure 39-3, pick the Data category on the left and select ADO.NET Entity Data Model. Enter a name for the model (such as OrdersModel.edmx) and click Add to launch the Entity Data Model Wizard.
The wizard's first page, which is shown in Figure 39-4, lets you decide whether you want to generate a model from a database or create an empty model. An empty model is useful if you want to build the model from scratch and then use Entity Framework tools to build a database from it.
For this example, pick "Generate from database" and click Next to display the Choose Your Data Connection page shown in Figure 39-5. If you have previously defined database connections (which you probably haven't at this point), you can select one from the drop-down list.
If you have no previously defined connections, click New Connection to display the Choose Data Source dialog shown in Figure 39-6. Select the type of data source that you want to use and click Continue. In Figure 39-6, I selected a database server, but the documentation says that Visual Studio Express Edition can select only database files, not servers, so you might need to make a different selection.
When you click Continue, the Connection Properties dialog shown in Figure 39-5 appears. Fill in the server name and database name. Note that you must append SQLEXPRESS to the server name if you are using SQL Server Express Edition. If you don't, you'll get a confusing error message saying, "The server was not found or was not accessible."
When you finish entering the server and database names, click OK to return to the Entity Data Model Wizard page shown in Figure 39-5. Click Next to see the page shown in Figure 39-8. Select all of the tables and click Finish to close the wizard.
At this point the wizard builds the data model and displays it graphically, as shown in Figure 39-9. In addition to building a pretty picture of the model, the wizard builds classes to represent the data. If you look closely at this figure, you can learn about those classes and how they are related.
If you close the data model editor shown in Figure 39-9, you can reopen it by double-clicking on the .edmx
file in Solution Explorer.
Each class is represented by a box that lists the properties built by the wizard to correspond to the database table's fields. At the bottom of each class is a list of navigation properties that your program can use to link one class to others. For example, the Customer
class's PurchaseOrders
property is a collection of PurchaseOrder
objects.
If you click on a property in the model, the Properties window displays information about that property such as its name, whether it is nullable (allows null or missing values), and its data type.
The links connecting the classes shown in Figure 39-9 indicate the relationships among the classes. The numbers at either end of a link indicate the numbers of objects from the corresponding classes that are involved in the link.
For example, the link between the Customer
and PurchaseOrder
classes in Figure 39-9 has a 1 at the Customer
end and a * at the PurchaseOrder
end. That means one Customer
object corresponds to any number of PurchaseOrder
objects. Similarly, one PurchaseOrder
object corresponds to any number of OrderItem
objects.
If you click a link, the Properties window provides information about the link such as the names of the fields in the two tables it connects and their multiplicities.
The model displayed in Figure 39-9 shows the main classes that represent database tables: Customer, PurchaseOrder
, and OrderItem
. There's one more key class that the Entity Data Model Wizard creates to represent the database as a whole: OrdersDatabaseEntities
. This class is named after the database (OrdersDatabase) with the word "Entities" added to the end.
To use the classes, create a new instance of the OrdersDatabaseEntities
class.
Because the entities object provides a context for manipulating the data, programmers often add "Context" to the name of that class's instance. For example, the following code creates an instance of the OrdersDatabaseEntities
class named OrderContext
:
' Create the database context. Private OrderContext As New OrdersDatabaseEntities()
After you create the entities object, you can use its properties to access objects representing the data in the database. In this example, the OrdersDatabaseEntities
object's Customers, PurchaseOrders
, and OrderItems
collections let you manipulate the data.
Note that you can use LINQ as described in Lesson 38 to filter and arrange the data. For example, you could use a LINQ query to select Customer
objects with certain FirstName
and LastName
values and then display the results in a list.
There are two final steps you can take to make the program more useful:
Call the context object's SaveChanges
method to save any changes that you have made to the data. For example, if you add, modify, or delete data objects, calling SaveChanges
will copy those changes to the database.
When the program is done with the database, it should call the context object's Dispose
method to free its resources.
Microsoft's "Getting Started (Entity Framework)" page at http://msdn.microsoft.com/en-us/library/bb386876
is a good starting point for learning more about the Entity Framework.
In this Try It, you add code to the OrdersDatabase example described so far in this lesson. You build the program shown in Figure 39-10 to display the orders placed by a customer and the items contained in each order.
You can download the code and resources for this Try It from the book's web page at www.wrox.com
or www.vb-helper.com/24hourvb.html
. You can find them in the Lesson39 folder of the download.
In this lesson:
Build the OrdersDatabase described in this lesson.
Build the user interface shown in Figure 39-10. The two large controls displaying order and order item data are DataGridView
controls.
Make a CustomerWithName
class to hold a Customer
object and its full name. Override its String
method so the ComboBox
at the top in Figure 39-10 can display the customer's full name.
When the program starts, create the data context object. Use LINQ to select the Customer
objects and make CustomerWithName
objects from them. Display them in the ComboBox
.
When the user selects a customer, display the customer's orders in the first DataGridView
.
When the user selects an order from the first DataGridView
, display its order items in the second DataGridView
.
Make the Save button save the data.
You can make a DataGridView
display a collection of data by setting its DataSource
property to the collection. It will figure out what rows and columns to display.
Watch out for customers with no orders or orders with no items.
To display CustomerWithName
objects in the ComboBox
, set the control's DataSource
property to an array of CustomerWithName
objects. Then set its DisplayMember
property to FullName
, the name of the property that you want the control to display.
Hide DataGridView
columns that are used only for navigation so the user doesn't see them, as in the following code:
dgvCustomerOrders.Columns("CustomerId").Visible = False
Build the OrdersDatabase described in this lesson.
Follow the instructions earlier in this lesson to build the SQL Server database. Download and use the MakeOrdersDatabase.sql
script if you like.
Build the user interface shown in Figure 39-10. The two large controls displaying order and order item data are DataGridView
controls.
This is reasonably straightforward. Make the ComboBox, Button
, and two DataGridView
controls as shown in Figure 39-10.
Make a CustomerWithName
class to hold a Customer
object and its full name. Override its String
method so the ComboBox
at the top in Figure 39-10 can display the customer's full name.
Use code similar to the following:
Public Class CustomerWithName Public Customer As Customer Public FullName As String Public Overrides Function ToString() As String Return FullName End Function End Class
When the program starts, create the data context object. Use LINQ to select the Customer
objects and make CustomerWithName
objects from them. Display them in the ComboBox
.
Use code similar to the following:
' The database context. Private OrderContext As OrdersDatabaseEntities ' Load the data. Private Sub Form1_Load() Handles MyBase.Load ' Load the data. OrderContext = New OrdersDatabaseEntities() ' Select the Customers. Dim customersQuery = From cust As Customer In OrderContext.Customers Order By cust.FirstName, cust.LastName Select New CustomerWithName With {
.Customer = cust, .FullName = cust.FirstName & " " & cust.LastName } ' Bind the Customers to the cboCustomer ComboBox. cboCustomers.DisplayMember = "FullName" cboCustomers.DataSource = customersQuery.ToArray() End Sub
When the user selects a customer, display the customer's orders in the first DataGridView
.
Give the ComboBox
a SelectedIndexChanged
event handler similar to the following:
' Display this customer's orders. Private Sub cboCustomers_SelectedIndexChanged() _ Handles cboCustomers.SelectedIndexChanged If cboCustomers.SelectedItem Is Nothing Then Exit Sub ' Get the selected Customer. Dim custWithName As CustomerWithName = DirectCast(cboCustomers.SelectedItem, CustomerWithName) Dim cust As Customer = custWithName.Customer ' Display this customer's orders. dgvCustomerOrders.DataSource = cust.PurchaseOrders ' Hide navigation properties. dgvCustomerOrders.Columns("CustomerId").Visible = False dgvCustomerOrders.Columns("Customer").Visible = False dgvCustomerOrders.Columns("OrderItems").Visible = False ' Size the columns. dgvOrderItems.AutoResizeColumns() ' Select the first order (if there is one). If dgvCustomerOrders.Rows.Count > 0 Then dgvCustomerOrders.Rows(0).Selected = True End If End Sub
When the user selects an order from the first DataGridView
, display its order items in the second DataGridView
.
Give the ComboBox
a SelectedIndexChanged
event handler similar to the following:
' Display this order's items. Private Sub dgvCustomerOrders_SelectionChanged() _ Handles dgvCustomerOrders.SelectionChanged If dgvCustomerOrders.SelectedRows.Count = 0 Then Exit Sub ' Display the selected purchase order's items. Dim po As PurchaseOrder = DirectCast( dgvCustomerOrders.SelectedRows(0).DataBoundItem, PurchaseOrder)
' Display this order's items. If po Is Nothing Then ' There are no items to display. dgvOrderItems.DataSource = Nothing Else ' Display this order's items. dgvOrderItems.DataSource = po.OrderItems ' Hide navigation properties. dgvOrderItems.Columns("PurchaseOrder").Visible = False dgvOrderItems.Columns("OrderId").Visible = False ' Size the columns. dgvOrderItems.AutoResizeColumns() End If End Sub
Make the Save button save the data.
Use code similar to the following:
' Save changes to the data. Private Sub btnSave_Click() Handles btnSave.Click Try OrderContext.SaveChanges() MessageBox.Show("Changes Saved", "Changes Saved", MessageBoxButtons.OK, MessageBoxIcon.Information) Catch ex As Exception Dim msg As String = ex.Message If ex.InnerException IsNot Nothing Then msg &= vbCrLf & vbCrLf & "***** Inner Exception *****" & vbCrLf & ex.InnerException.Message End If MessageBox.Show(msg) End Try End Sub
Please select Lesson 39 on the DVD to view the video that accompanies this lesson.
The Try It uses the CustomerWithName
class to enable its ComboBox
to display customer first and last names while also storing a reference to the corresponding Customer
object. This is somewhat awkward and makes the program convert back to a Customer
object when the user selects a new CustomerWithName
object.
A more elegant solution is to override the ToString
method in the Customer
class so you can display Customer
objects directly in the ComboBox
. Because the Customer
class is automatically generated, you don't really want to mess with its code. However, you can add new methods to the class by using the Partial
keyword. That keyword tells Visual Basic that you are defining a class, but part of the class is defined elsewhere.
The following code adds the necessary ToString
override to the Customer
class:
Partial Public Class Customer Public Overrides Function ToString() As String Return FirstName & " " & LastName End Function End Class
Copy the solution you built for the Try It and add this code. Delete the CustomerWithName
class and modify the program to work directly with Customer
objects.
Copy the program you built for Exercise 1 and add a New Customer button. When the user clicks the button, display a dialog in which the user can enter first name, last name, and customer ID values. If the user clicks OK, create a new Customer
object and add it to the context object's Customers
collection by calling its AddObject
method. Then call the context object's SaveChanges
method.
Hint: To show the new customer, move the code that uses LINQ to build the customer list into a new subroutine and call it after you add the new customer.
Build a program similar to the one you built for Exercise 1 to display student test scores. The Student table should have fields FirstName, LastName
, and StudentId
. The TestScore table should have fields StudentId, TestNumber
, and Score
. You can download the MakeTestScoresDatabase.sql
script from the book's web page to make building the database easier.
The program should display the students' names in a ComboBox
. When the user selects a student, it should display that student's scores in a DataGridView
.
Copy the program you built for Exercise 3 and add two RadioButtons
to the form. Label them All Students and Failing Students. Move the code that loads the ComboBox
into a DisplayStudents
subroutine and make it check the RadioButtons
to see what LINQ query to use in selecting the students.
Give the Student
class a new Average
function that uses LINQ to return the student's test score average. When the Failing Students RadioButton
is checked, make the DisplayStudents
subroutine use the following LINQ query:
Dim failingStudentsQuery = From stu As Student In ScoresContext.Students.ToArray() Order By stu.FirstName, stu.LastName Where stu.Average() > 60 Select stu
You can find solutions to this lesson's exercises in the Lesson39 folder inside the download available on the book's web site at www.wrox.com
or www.vb-helper.com/24hourvb.html
.