Julie Lerman
Published: March 2011
Download the code for this article:
See a video of this example in action.jpg)
The ADO.NET Entity Framework provides data access for developers creating a variety of application types. Whether you are looking for Rapid Application Development (RAD) capabilities or building highly architected enterprise applications, you can find an entry point in EF that suits your development needs.
EF integrates with the various design tools in Visual Studio 2010 for building RAD applications, whether you are targeting ASP.NET Web Forms, Dynamic Data web sites, Windows Forms or Windows Presentation Foundation (WPF). In this whitepaper, you will focus on building a RAD WPF application using EF and the advanced capabilities of WPF drag and drop data-binding that were introduced in Visual Studio 2010.
Frequently, RAD applications include every bit of logic in a single project. We’ll start with such an application to demonstrate a feature that is only available when your EDM is created inside of the WPF Application project. The application will present data from a sample database with Customer, Orders, Line Item and Product details. The database, Customers.mdf, is included in the download that accompanies this whitepaper.
Create a new WPF Application project using the following steps:
Visual Studio will create a new WPF application with a WPF Window. The new Window will automatically open up in design view in the IDE as shown in Figure 2.
.png)
Figure 1
Now that you have the beginnings of a WPF Application, it’s time to create the Entity Data Model.
This walkthrough presumes that you have already downloaded the Customers database file along with the source code for this article.
There are two ways to create an Entity Data Model. One is by adding a new ADO.NET Entity Data Model from the Add New Item wizard. Another is to begin by adding a database file into your project. Since we’ll be working with a database file rather than connecting to a SQL Server database, you’ll follow the second path.
The Entity Data Model Designer will open up with the following model displayed.
.png)
Figure 2
With the model created, select the MainWindow.xaml tab in designer so that you can begin designing the WPF window using the model. WPF uses Data Sources for binding data to controls on a window. Because you created the model inside of the WPF project, Visual Studio automatically created Data Sources for you from the entities in the EDM. If your model is in a separate project, then you would need to create the Data Sources manually. In that case, you would create “Object” Data Sources which rely on the entity classes generated from the model. See the MSDN topic, Data Source Configuration Wizard, at http://msdn.microsoft.com/en-us/library/w4dd7z6t.aspx.
1. Open the Data Sources window by selecting Data from the Visual Studio menu and then Show Data Sources. You’ll see all three entities listed in the window.
2. Expand Customer and you can all of its properties as shown in Figure 3.
.png)
Figure 3
Notice that Orders navigation property is listed as one of the properties under Customers. If you expand this you’ll see its properties, including LineItems. You have access to an entity’s complete hierarchy in the Data Sources window.
These navigation properties are not the same representation as their entity counterparts. The Orders listed inside the Customers entity represents Orders of particular Customers. The Orders item that is at the same level as the Customers represents the complete universe of Orders. This is an important distinction which will become clearer as you perform the data-binding tasks.
The Data Sources provide your application with an understanding of class structure so that you can design data-bound applications. You now have everything you need in place to observe the magic of WPF’s most basic drag and drop features.
If you look closely at the Data Sources you should see a small grid icon to the left of each of the entities. Every property also has an icon to represent a WPF control type next to it. By default, String properties are represented as TextBoxes, Lists or Collections are represented by a DataGrid and so forth. The icon indicates what the default control will be when you drag the particular item (an entire class or a property) onto the form. You can modify these defaults but we’ll get around them a different way.
As a result, WPF will bind the Customers to this ListBox. You won’t see any visible change on the form, but in the XAML below, you should notice that something has changed. The Grid element representing the Window has acquired a DataContext attribute as shown in Figure 4.
.png)
Figure 4
By default, the ListBox will choose to display the first property it finds that is not an identity property. That means your new ListBox will list only titles of the customers, e.g., Mr., Mrs., Dr.. You can choose a different property to display either by modifying the XAML directly or using the Properties window. Let’s do the latter.
Next you’ll turn this into a master/detail view by adding the Orders to the window as a DataGrid.
The form will look something like Figure 5. I’ve resized the ListBox and DataGrid to make this more visually appealing.
.png)
Figure 5
With no additional effort, not even a line of code, you can now run this application and see some data.
Not every customer in this database has orders so you’ll need to poke around to see customer orders populate the grid. Figure 6 shows some of the sample data.
.png)
Figure 6
Remember that the fact that your application is able to access the database without your having written a single line of code is a special super-RAD feature of WPF that occurs only when the EDM is an item in the WPF project. But the price you pay for this automation is that you have no control over how much data is being returned. If you were to watch the database activity in a profiler you would see that only one query was executed while running the application. The query returns every customer in the database along with their orders into memory. As you move from one customer to the next and see the different orders display in their grid, all of that data is coming from memory. This is the nature of this type of magical drag and drop data-binding. It’s a great benefit for building quick applications where you are not concerned with performance or resource usage.
In a more structured application, the EDM would be in a separate project and this magic would not occur. You’d have to do a little more work – writing code to define and execute queries then bind the results to the source which populates the controls on the form. Let’s take this example a bit further and see how to populate the controls using your own queries. First, you’ll need to take a look at what you are currently working with and then you’ll be able to modify it.
WPF uses a class called CollectionViewSource to act as an agent to coordinate between Data Sources and the data-binding controls. (Bea Stollnitz, a former member of the WPF team at Microsoft, has a great blog post explaining the CollectionViewSource at http://bea.stollnitz.com/blog/?p=387.) One of the convenient features of WPF’s drag & drop data-binding is that it will create most of the infrastructure for you, including the necessary CollectionViewSource controls. As you saw in the window you just built, it will even create the necessary code to get the data from the database.
Take a look at the code-behind the WPF window that you are working with. You can do this by right clicking the window and selecting View Code from the context menu.
In the code-behind, you’ll see two methods, Window_Loaded and GetCustomersQuery. These were created when you dragged and dropped the Customers Data source onto the window. Be sure you have stopped debugging the application before moving forward with this walk through.
GetCustomersQuery uses an EF ObjectQuery to define a query which retrieves all of the Customer entities.
System.Data.Objects.ObjectQuery<CustomersWPF.Customer> customersQuery = customersEntities.Customers;When you first dragged the Customers data source onto the form, the query was simply defined to retrieve customers.
Then when you dragged the Orders navigation property onto the form, Visual Studio added the next line of code.
customersQuery = customersQuery.Include("Orders");This second bit of code adds an EF Include method to the first query . Include will cause EF to eager load every customer’s collection of orders from the database at the same time the customers are retrieved.
The Window_Loaded method performs a number of tasks. Here’s the code:
CustomersEntities customersEntities = new CustomersEntities();
CollectionViewSource customersViewSource =
((CollectionViewSource)(FindResource("customersViewSource")));
ObjectQuery<Customer> customersQuery = GetCustomersQuery(customersEntities);
customersViewSource.Source = customersQuery.Execute(MergeOption.AppendOnly);Here is what the code achieves.
Note that by default, this automatically generated code with fully qualified class names, e.g., System.Data.Objects.ObjectQuery. I’ve added the necessary namespaces into the using declarations at the top of the class (e.g., using System.Data.Objects and using System.Windows.Data) so that the code in the methods is cleaner.
Notice that there is no code in there to specify that the Orders data should be bound to the grid that you dragged onto the window. This is determined by the XAML. The XAML DataGrid has an attribute, ItemsSource, that binds itself to another element called customersOrdersViewSource. And that element is bound to the Orders property of the CustomersViewSource which, as you’ve just seen in the code-behind, is bound to the results of the query.
Here is the XAML code that defines both ViewSources:
<Window.Resources>
<CollectionViewSource x:Key="customersViewSource"
d:DesignSource="{d:DesignInstance my:Customer, CreateList=True}" />
<CollectionViewSource x:Key="customersOrdersViewSource"
Source="{Binding Path=Orders, Source={StaticResource customersViewSource}}" />
</Window.Resources>As you can now see, the data-binding is controlled by a combination of XAML code and .NET API calls in the code-behind. It is possible to specify 100% of the data-binding in the XAML or even 100% of it in the code-behind depending on your coding patterns.
Now that you have explored the default query in the code-behind, you can overwrite the automatically created query with one that is more specific to your needs. For example, rather than executing a query that returns every single customer along with order information, let’s modify the query to return only those customers that have orders. And rather than eager loading all of those orders at once, you can take advantage of Entity Framework’s lazy loading to load the orders only as needed.
Since this is not a disconnected application, allowing the WPF form to trigger lazy loading of data (e.g., automatically returning to the database as needed for related data) is an acceptable practice.
using System.Data.Objects;private ObjectQuery<Customer> GetCustomersWhoHaveOrders(CustomersEntities custEntities)
{
var customersQuery = custEntities.Customers.Where(c => c.Orders.Any())
.OrderBy(c => c.CompanyName);
return customersQuery as ObjectQuery<Customer>;
}The query within the method combines a Where method with LINQ’s Any method so that only customers who have any Orders are retrieved. The Any method is not a LINQ to Entities method. It is a LINQ to Objects method and causes the results to be an IQueryable. This is why the last line of code casts the query back to an ObjectQuery. We want to retain the ObjectQuery so that we can still use the Execute method which is more appropriate for databinding.
Back in the Window_Loaded method, you can modify the data-binding by removing (or commenting, as I’ve done) the two lines of code that create and bind the original customersQuery and replace those with code which binds to the query returned by the new method.
//ObjectQuery<Customer> customersQuery = GetCustomersQuery(customersEntities);
//customersViewSource.Source = customersQuery.Execute(MergeOption.AppendOnly);
customersViewSource.Source =
GetCustomersWhoHaveOrders(customersEntities).Execute(MergeOption.AppendOnly);Now when you run this application, you will see only those customers that have orders in the list view as shown in Figure 7.
.png)
Figure 7
The application now initially queries only for the specified list of customers. Then as a customer is selected in the ListBox on the left, Entity Framework uses lazy loading to retrieve the orders for that particular customer which then get bound to the DataGrid on the right. Thanks to the data-binding and Entity Framework’s relationship management, there is no need for you to be concerned with how to navigate the relationship between Customers and their Orders or write all of the code to get that data into the hierarchical view on the WPF window.
There is only one small barrier to tracking user edits in your current application. The ObjectContext is declared in the Window_Loaded event and therefore is not accessible from other methods. How would you call SaveChanges in a different method such as a Button Click event? To fix this you can move the line of code that declares and instantiates CustomerEntities from the Window_Loaded event to the window declarations. Here you can see that line of code in its new position.
public partial class MainWindow : Window
{
CustomersEntities customersEntities = new CustomersEntities();
public MainWindow()
{
InitializeComponent();
}
}Pair with this code to ensure that the context is disposed immediately when the window is closed. To create the method that ties to the window’s Closing event, you’ll need to return to the designer.
private void Window_Closing(object sender, CancelEventArgs e)
{
customersWEntities.Dispose();
}.png)
Figure 8
Now that the customersEntities context is accessible throughout the class, you can add a Save button to the Window and ensure that customersEntities.SaveChanges is called when a user clicks that button. Here’s how to get this logic into your application:
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
customersEntities.SaveChanges();
}You can run the application again and test out the saving feature. Since the Customers ListBox doesn’t provide you with editing capabilities, you can modify one of the Orders.
The Order entity has a number of DateTime properties. When you dropped Order onto the window, the WPF designer automatically created DatePicker elements to represent them. Unlike the CheckBox element that you just used to modify the Online Order Flag, the DatePicker does not automatically notify the CollectionViewSource of a change to its value. You will need to specify this behavior by adding the UpdateSourceTrigger attribute to the SelectedDate property of the DatePicker, setting its value to PropertyChanged. The modified element for the OrderDate property is shown here:
<DatePicker SelectedDate="{Binding Path=OrderDate, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=true,
NotifyOnValidationError=true}" />With this change in place as well as a similar fix to the other DatePicker controls in the DataGrid, you can ensure that edits to the date properties will also be tracked and persisted to the database upon calling SaveChanges.
Modifying the related entities (Orders) is different than modifying the collection of those related entities. You just modified entities by editing their properties. Modifying the collection, however, means adding or removing items from the collection, in other words, inserting new Orders or removing existing ones.
Adding new Order items to a customer is straightforward. The user can enter data into the provided empty row in the Orders DataGrid. The CollectionViewSource will automatically pick up the fact that you’ve entered this information and pass that along to the ObjectContext. When the user clicks the Save button, the resulting call to SaveChanges will cause the new row to be inserted into the database.
The WPF bindings will take care of providing the CustomerID to the new Order. Here’s a demonstration of how that works.
Figure 9 shows a partially entered Order:
.png)
Figure 9
If at this point, the user leaves the row and selects another row, as in Figure 10, notice that the CustomerID in the new row has been automatically populated with the value of the CustomerID, 6. WPF inserted the CustomerID for you and moving to a new row forced the grid to display that value.
.png)
Figure 10
After the row is saved and inserted into the database, the Entity Framework will return the OrderID and calculated column value (Sales Order Number) for the new entity and it will be displayed in the grid, as you can see in Figure 11.
.png)
Figure 11
In a production application, you would most likely not expose the OrderID or CustomerID and you would make the SalesOrderNumber a read-only column.
You can use the Visibility property to hide an element while retaining the critical values. Here is the XAML for the OrderIDColumn with its Visibility set to “Hidden”
<DataGridTextColumn x:Name="orderIDColumn" Binding="{Binding Path=OrderID}"
Header="Order ID" Width="SizeToHeader" Visibility="Hidden" />Making an element read-only, is just as simple, using the IsReadOnly property:
<DataGridTextColumn x:Name="salesOrderNumberColumn"
Binding="{Binding Path=SalesOrderNumber}" Header="Sales Order Number"
Width="SizeToHeader" IsReadOnly="True" />You can make these changes by typing directly in the XAML or using the Properties window when you have selected the particular element in the XAML.
There’s one problem you may notice if you are following the walkthrough. The DatePicker control behaves strangely when entering new rows and does not trigger
Entering the date values into new rows using the DatePicker controls poses some challenges. The root of the problem occurs when modifying the dates in a placeholder row when the row is not in edit mode. This scenario does not trigger the row to go into edit mode and the date values will not stick. There is no single setting in the DatePicker to fix this problem. Instead you can modify the XAML so that the display mode of the date will be a TextBlock and edit mode will be a DatePicker and this will result in the proper behavior.
Recall the column template for the Order Date column.
<DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker
SelectedDate="{Binding Path=OrderDate, UpdateSourceTrigger=PropertyChanged,
ValidatesOnExceptions=true, NotifyOnValidationError=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>The DataGridTemplateColumn.CellTemplate is the default and is being used both for display and edit purposes.
Replace the element within the CellTemplate with a TextBlock that binds to the OrderDate property:
Then below it you can add another template, this time a CellEditingTemplate. In here, you can use the DatePicker with the same properties that you had previously.
Here is the new OrderColumn now with the CellTemplate and CellEditingTemplate sections
<DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=OrderDate, StringFormat=\{0:d\}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding Path=OrderDate, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,ValidatesOnExceptions=true,
NotifyOnValidationError=true}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>Make the same change to the other date columns.
There is still one last problem which you will solve with a modification to the Order class.
In a new row, the date fields will by default be set to .NET’s DateTime.MinimumValue which is January, 1, 0001. The non-nullable date fields, Order Date and ModifiedDate will be automatically set to those values.
Since this walkthrough is using default code generation to create the entity classes, you can use partial classes to supply additional logic. In this case you will create a constructor for the Order class that will set the values for the date fields in a new row. This will not impact the values of entities queried from the database. Follow these steps to add this logic to your application.
Here is the partial class which sets default values for the Order’s date fields.
namespace CustomersWPF
{
public partial class Order
{
public Order()
{
OrderDate = DateTime.Today;
ModifiedDate = DateTime.Today;
DueDate = DateTime.Today.AddMonths(1);
}
}
}Now when you run the application and start working in a new row, the dates will be populated with the new defaults. Additionally, you will only see a DatePicker control in a date field that is currently being edited as you can see in Figure 12.
.png)
Figure 12
Deleting rows from the related entities collection is not quite as straight-forward as inserting. The reason is that there is a big difference between what a user’s intentions may be when deleting a row from the Order DataGrid and how .NET and the Entity Framework interpret that action.
As far as .NET and EF are concerned, when a row is deleted from the DataGrid, the item is being removed from the Orders collection for that particular Customer. Technically, the CustomerID property of that Order is set to 0 and there is no longer a relationship between the Customer and that particular Order. When the user then clicks the Save button, Entity Framework sends an UPDATE command to the database to update the Order.CustomerID foreign key value to 0. Because the database is defined to enforce that an Order belongs to a Customer and that the CustomerID value cannot be 0 or null, an exception will be returned and the SaveChanges command will fail.
This is not a behavior that is specific to either WPF or to the Entity Framework. You would get the same behavior, for example, if you were working with a DataTable in a Windows Form application.
There are cases where removing the relationship is exactly the intent. For example, what if your application kept track of students and classes? The form might show a list of students and in the details DataGrid, all of the classes that a particular student is enrolled in. A student might decide not to take a particular class that he was enrolled in, so you would need to remove the class from the collection of the student’s classes. You don’t want to delete the class from the database but simply remove the relationship between the student and class. If this was the form you were building, the end user can simply highlight the desired row in the DataGrid, hit the Delete key on her keyboard and then click the Save button. It just works with no extra effort on the part of you, the developer.
However, in the scenario of Customers and Orders, it is most likely the case that in deleting the row from the DataGrid, the user intends to delete the Order from the database (along with the line items associated with the Order). If you want the user to be able to remove the order, perhaps to move to a different customer, you should probably create a specific application feature for that edge-case action.
It makes more sense that when using the Delete key, the user intends to delete the selected item. Let’s modify the application so that this action does, in fact, delete, and not simply remove, the selected Order.
Remember, though that .NET and EF will interpret that as Remove, so you’ll need to provide some additional logic to ensure that the row gets deleted from the database when the user deletes.
There are a few ways to approach this problem. Unfortunately, the DataGrid doesn’t have an event to capture the fact that a user has deleted a row from the grid and you will have to do a bit more coding to properly handle when a user deletes an Order.
Entity Framework’s EntityCollection class exposes an event called AssociationChanged. The Orders navigation property in your model is an EntityCollection, so you can take advantage of this event to solve the problem.
AssociationChanged will let us know when a new relationship has been created between entities (such as when an Order is added to a Customer) or when a relationship has been removed (e.g., an Order is no longer related to Customer). It is just the thing we need in order to be aware of when someone removes a row from the DataGrid, and therefore the Customer.Orders collection, so that we can force that order to be deleted instead. But this will mean making a few changes to how the current application behaves.
Most importantly, it will require that you have explicitly control over the Orders collection for a customer. Currently, the XAML data-binding in combination with EF’s lazy loading is taking care of the collection in the code that defines the customersOrdersViewSource and its relationship to the customersViewSource.
<CollectionViewSource x:Key="customersOrdersViewSource"
Source="{Binding Path=Orders, Source={StaticResource customersViewSource}}" />Remove the Source attribute so that the CollectionViewSource is declared but nothing more.
<CollectionViewSource x:Key="customersOrdersViewSource" />Now the automatic data-binding and lazy loading will no longer work. It’s up to you to specify the binding in the code behind, which you’ll do now.
Every time a user selects a different customer from the ListBox, you’ll need to get a new set of Orders and bind them to the CollectionViewSource which will in turn bind them to the DataGrid.
At the same time you can perform all of the other necessary tasks. Start by creating a method to capture when the user selects a new customer.
private void CustomerListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//Extract the selected customer from the EventArgs
var selectedCustomer = (Customer) e.AddedItems[0];
//Load customer's orders if necessary and bind event handler.
if (selectedCustomer.Orders.IsLoaded == false)
{
selectedCustomer.Orders.Load();
selectedCustomer.Orders.AssociationChanged += OrdersCollection_AssociationChanged;
}
//Identify the CollectionViewSource for the Orders in XAML
var ordersViewSource = ((CollectionViewSource)
(this.FindResource("customersOrdersViewSource")));
//Bind the ObservableCollection to the CollectionViewSource
ordersViewSource.Source = selectedCustomer.Orders;
}By testing for IsLoaded , you can be sure not to make unnecessary calls to the database to get the required orders into memory as well as avoid binding the event handler multiple times.
void OrdersCollection_AssociationChanged(object sender,
System.ComponentModel.CollectionChangeEventArgs e)
{
if (e.Action==CollectionChangeAction.Remove)
{
//force delete
aWEntities.Orders.DeleteObject(e.Element as Order);
}
}This method determines if the change made to the collection was that an item was removed and if that is the case, then it casts that item to an Order and explicitly marks it to be deleted.
customersEntities.ContextOptions.LazyLoadingEnabled = false;Now when the SaveChanges method is called, a DELETE command will be sent to the database. A cascade delete rule defined in the database will automatically delete all of the LineItem rows belonging to the Order being deleted.
Visual Studio 2010 brought some wonderful new features to developers working with WPF, especially the greatly enhanced data binding support. Enabling developers to easily build WPF windows or even master detail windows with WPF with little or even no code at all means that you can quickly get started with RAD line-of-business apps that take advantage of Entity Framework’s conceptual model, querying and change tracking features.
In this article you’ve seen how to easily drag and drop entities onto a WPF form and use those forms right away. You’ve seen how you can take more control over how queries are executed over the database and easily allow users to edit that data. Finally you looked at some of the nuances of working with related collections where insert new data is simple, but a small tweak is necessary to specify that the intent of the application is to delete orders when a user deletes them from the DataGrid rather than creating an orphaned item
You’ve also gained insight that will help you move forward when it’s time to move from RAD applications to building enterprise level applications with WPF and the Entity Framework.
Julie Lerman is a Microsoft MVP, .NET mentor and consultant who lives in the hills of Vermont. You can find her presenting on data access and other Microsoft .NET topics at user groups and conferences around the world. Julie blogs at thedatafarm.com/blog and is the author of the highly acclaimed book, “Programming Entity Framework” (O’Reilly Media, 2009). Follow her on Twitter.com: julielerman.