Using the WPF ObservableCollection with EF Entities
The ObservableCollection is a special WPF collection that provides proper notifications to the UI when items are added, removed, or the list is refreshed because it implements INotifyCollectionChanged. It’s common to use this collection (or inherit from it) to contain your business objects you want to bind to in WPF.
Class Window1
Private CustomerData As ObservableCollection(Of Customer)
You can then set up a CollectionViewSource and use it’s View property to get a reference to the ListCollectionView in order to add and remove items instead of working with the source collection directly. This decouples your data source (and therefore any collection logic) from the form itself making it much easier to change sources later. I’ve showed how to use CollectionViewSources before but basically you just declare them in the Window.Resources section and bind to them in XAML:
<Window x:Class="Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="282" Width="440" Name="Window1">
<Window.Resources>
< CollectionViewSource x:Key ="CustomerSource" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource CustomerSource } }">
And then you can set the Source property in code to your collection and obtain the ListCollectionView.
Dim customerSource = CType(Me.Resources("CustomerSource"), CollectionViewSource)
customerSource.Source = Me.CustomerData
Me.View = CType(customerSource.View, ListCollectionView)
Then you use the View to add and remove items from the collection and the UI will update properly:
Private Sub btnDelete_Click() Handles btnDelete.Click
If Me.View.CurrentPosition > -1 Then
'removes the currently selected customer from the underlying collection
Me.View.RemoveAt(Me.View.CurrentPosition)
End If
End Sub
Private Sub btnAdd_Click() Handles btnAdd.Click
'adds a new customer to the underlying collection
Dim customer = CType(Me.View.AddNew, Customer)
'do something with customer if needed...
Me.View.CommitNew()
End Sub
Calling these methods on the ListCollectionView will execute the InsertItem and RemoveItem methods on the ObservableCollection.
Now if you are using an Entity Data Model (EDM) the designer in Visual Studio 2008 SP1 will generate entity classes for you that you can also bind to in your UI. Access to these entities is done through the ObjectContext and the designer also creates a class for you that inherits from this when you create the EDM. It is named something like xxxEntites. (For instance, in Visual Studio 2008 SP1 “Add New Item” and select ADO.NET Entity Data Model and name it Northwind.edmx. Generate from Database and select Northwind. Select all the tables and then the designer will generate an ObjectContext called NorthwindEntities and entity classes based on the tables in the database.)
Because the ObjectContext is what tracks changes on entities you can place entities inside an ObservableCollection but in order for the ObjectContext to be notified that adds and deletes need to be tracked you need to write a bit of code. The easiest thing to do is to create your own class that inherits from ObservableCollection and override the InsertItem and RemoveItem methods so that you can tell the ObjectContext to either add or delete the entity which will ultimately execute against the database. In the constructor pass a reference to the ObjectContext. You can also pass in any collection of entities, say from a LINQ query, and then add them to the ObservableCollection. For example:
Imports NorthwindDAL
Imports System.Collections.ObjectModel
Public Class CustomerCollection
Inherits ObservableCollection(Of Customer)
Private _context As NorthwindEntities
Public ReadOnly Property Context() As NorthwindEntities
Get
Return _context
End Get
End Property
Sub New(ByVal customers As IEnumerable(Of Customer), ByVal context As NorthwindEntities)
MyBase.New(customers)
_context = context
End Sub
Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As Customer)
Me.Context.AddToCustomers(item)
MyBase.InsertItem(index, item)
End Sub
Protected Overrides Sub RemoveItem(ByVal index As Integer)
Me.Context.DeleteObject(Me(index))
MyBase.RemoveItem(index)
End Sub
End Class
Then you can use the collection on your WPF form instead like so:
Imports NorthwindDAL
Class Window1
Private db As New NorthwindEntities
Private CustomerData As CustomerCollection
Private View As ListCollectionView
Private Sub Window1_Loaded() Handles MyBase.Loaded
Dim results = From c In db.Customers _
Where c.City.ToLower = "seattle" _
Order By c.LastName, c.FirstName _
Select c
Me.CustomerData = New CustomerCollection(results, db)
Dim customerSource = CType(Me.Resources("CustomerSource"), CollectionViewSource)
customerSource.Source = Me.CustomerData
Me.View = CType(customerSource.View, ListCollectionView)
End Sub
Private Sub btnSave_Click() Handles btnSave.Click
Try
db.SaveChanges()
MsgBox("Customer data was saved.")
Catch ex As Exception
MsgBox(ex.ToString())
End Try
End Sub
Private Sub btnDelete_Click() Handles btnDelete.Click
If Me.View.CurrentPosition > -1 Then
Me.View.RemoveAt(Me.View.CurrentPosition)
End If
End Sub
Private Sub btnAdd_Click() Handles btnAdd.Click
Dim customer = CType(Me.View.AddNew, Customer)
'do something with customer if needed...
Me.View.CommitNew()
End Sub
End Class
Now any updates, adds or deletes you make in the UI will be propagated to the database through the Entity Framework.
Enjoy!
Comments
Anonymous
May 08, 2009
PingBack from http://microsoft-sharepoint.simplynetdev.com/using-the-wpf-observablecollection-with-ef-entities/Anonymous
May 09, 2009
Don't understand why you just don't add entities directly to your observable collectioin instead of adding them to the collection view. Wouldn't the collectionview automatically reflect those changes instead of having to create that observable collection inherited class.Anonymous
May 09, 2009
Very timely article. ThanksAnonymous
May 10, 2009
wow... thankz for your informations.... nice article...Anonymous
May 10, 2009
You could further simplify the code for the inherited ObservableCollection classes by getting rid of the IsLoading field and checks on insert by loading the collection via the base class: Sub New(ByVal customers As IEnumerable(Of Customer), ByVal context As NorthwindEntities) MyBase.New(customers) _context = context End SubAnonymous
May 11, 2009
Hi Rico, Views allow the same data collection to be displayed in the UI in different ways, depending on sorting, filtering, or grouping criteria. Calling AddNew on the view gives you transacted adding (with CommitNew/CancelNew) and maintains the filter, sort,etc. It also separates your model (source collection) from your presenter (UI). HTH, -BAnonymous
May 12, 2009
Hi Keith, I totally forgot about the base constructor, thanks! I updated the code. You may want to expose IsLoading as a readonly property for some other reason but we don't need it here. Cheers, -BAnonymous
May 15, 2009
I’ve been writing a lot about building WPF business applications with Entity Framework using Visual StudioAnonymous
May 16, 2009
I created a master/detail viewmodel based on your articles and your video. Below is the link to an article about it on my blog: http://reyntjes.blogspot.com/2009/04/master-detail-viewmodel_24.html Maybe you like it. Anyway, thanks for the articles. RobertAnonymous
June 19, 2009
A question. This is great when the action is initiated by the user and thus through the UI. What about the other way? If there is an update in the database, how to propogate that to the UI? Example there would be a timer where a lookup is made to the database and data pulled up. Where do we go about changing the collection so that the new/changed data reflects on the ui. What I am doing now is not elegant and would like to know a best practices way of doing this and if you know of any posts that go through this. (right now I am pulling up a list of simple objects that contain the data over wcf and then generating an observable collection and setting up the ui through a collectionviewsource. On the update thread another collection comes through and now I have to reconcile the two. Just looking to know what would be the best approach.) ThanksAnonymous
October 15, 2009
Thanks! Here is the VB version of it. Imports System.ComponentModel Imports System.Collections.ObjectModel Public Class SortableObservableCollection(Of T) Inherits ObservableCollection(Of T) Public Overloads Sub Sort(Of TKey)(ByVal KeySelector As Func(Of T, TKey)) Sort(KeySelector, ListSortDirection.Ascending) End Sub Public Overloads Sub Sort(Of TKey)(ByVal KeySelector As Func(Of T, TKey), ByVal dir As ListSortDirection) If dir = ListSortDirection.Ascending Then ApplySort(Items.OrderBy(KeySelector)) Else ApplySort(Items.OrderByDescending(KeySelector)) End If End Sub Public Overloads Sub Sort(Of TKey)(ByVal KeySelector As Func(Of T, TKey), ByVal comparer As IComparer(Of TKey)) ApplySort(Items.OrderBy(KeySelector, comparer)) End Sub Private Sub ApplySort(ByVal sortedItems As IEnumerable(Of T)) Dim lst = sortedItems.ToList For Each item As T In lst Move(IndexOf(item), lst.IndexOf(item)) Next End Sub End Class Example of how to use it: Public Class Chicken Public Property Name() As String ...... End Class Public Class IHasLotsaChickens Inherits SortableObservableCollection(Of Chicken) Public Sub SortMaChickens() Me.Sort(Function(chic) chic.Name, ListSortDirection.Descending) End Sub End Class haha :DAnonymous
December 02, 2009
Using your collection example with Linq2SQL I am having an issue with the timestamp not updating from database. The timestamp is required for the stored procedure to be refreshed after update. The Timestamp retreival works if I dont use the collection.Anonymous
September 20, 2010
Thanks, this is the simplest workaround I have yet seen for a decent EDM-WPF link! Awesome.Anonymous
November 11, 2010
Hi Beth, Thanks for your very interesting article. Overriding the InsertItem and RemoveItem methods in the ObservableCollection certainly is an easy way to get the data into your ObjectContext from a System.Windows.Controls.DataGrid. However, using this method, when a new row is added in the DataGrid the data goes directly into your ObjectContext (and from there, presumably, into your database). This is problematic if you want to allow the user to add a row, make some edits, and then either commit or cancel the add operation. (The commit or cancel are invoked by moving to a new row or by pressing ESC twice). InsertItem will be called by ListCollectionView.AddNew, and RemoveItem will be called by CancelAdd, but this does not fully reflect the semantics of AddNew / CancelNew / CommitNew. For example, if the power goes out or if the user goes out to lunch, you can easily end up with an incompletely edited row in your database. One way to address these questions would be to override ListCollectionView.AddNew, CommitAdd, and CancelAdd and keep the new row in a buffer unti the CommitAdd occurs. Unfortunately, these functions are not virtual .... at least not in the usual sense. They are declared in one of the interfaces that ListCollectionView implements. Of course one could try to re-implement ListCollectionView by forwarding all the method calls, but that is obviously a big hassle. What I recently found out is that interface members can be overridden, with one important caveat: the overriding function can only be called through the interface, not the base class. There's an excellent article about it in www.williamcaputo.com/.../000138.html . In the context of collection views, what we can do is derive from ListCollectionView and implement an interface, let's call it IListCollectionView, that consists of all the interfaces already implemented by ListCollectionView (and its base classes). Seems a bit redundant, I agree ... but it allows us to redefine IListCollectionView.AddNew/CancelNew/CommitNew without having to explicitly redefine all the other members and properties of ListCollectionView. What we can do is use IListCollectionView as the type of object returned by the view model (MVVM paradigm). We just bind our DataGrid to the IListCollectionView, and when it calls AddNew/CommitNew/CancelNew it will invoke the overridden function rather than the original. Best wishes, SteveAnonymous
December 21, 2010
Hi, Is it usable for binding binary data like images which are stored in DB?Anonymous
April 13, 2013
Can we have a c# version of this code ? I tried Telerick vb to c# converter but the compiler gives an error. protected override void RemoveItem(int index) { this.Context.DeleteObject(this(index)); base.RemoveItem(index); } Error says "Method name expected" for "this" inside DeleteObject method callAnonymous
December 16, 2013
Nice Post.. It was really helpful. But would like have ObservableCollection directly generated from EF itself :).Anonymous
April 30, 2016
The comment has been removed