Quick tips for Entity Framework Databinding
One of our customers asked this question yesterday on the Entity Framework forums. There were a few details missing and so I am not completely sure I got the question right. But I think it is about an issue I have heard quite a bit, and so I think it may be useful to share my answer here for others.
Given this simple line of code (i.e. in a WinForms application):
grid.DataSource = someQuery;
Several things will happening under the hood:
- Databinding finds that ObjectQuery<T> implements the IListSource interface, then it calls IListSource.GetList() on it, to obtain a binding list (an internal object that implements IBindingList and some other well-known interfaces that databound controls know how to communicate with)
- GetList() gets the query executed and it copies the contents of the resulting ObjectResult object to the binding list
- The new binding list is finally passed to the databound control
When a binding list is created this way from a tracked query (i.e. MergeOption in the query is set to anything but NoTracking), there are several other interesting details in the behavior:
- Changes made to entities in the binding list will get noticed by the state manger. Therefore changes will be saved to the database when SaveChanges is invoked on the tracking ObjectContext
- Additions of new entities to the context will not result in the new entities being added automatically to a binding list (this is a common expectation). Whether an entity belongs to a binding list is decided at the time the binding list is created. If we wanted to do something different (i.e. have the binding list get new objects added to the context automatically), we would hit some considerable obstacles:
- The binding list does not remember which filter condition was used in the query.
- Even if it did, our queries are server queries, expressed in either ESQL or LINQ, but always translated to server queries and then expressed in terms of the current values on the server.
- Even for LINQ queries, we cannot assume that the same query will have equivalent behavior while querying in-memory objects when compared to the same query translated to the native SQL of the database.
- As a consequence of #2: You can have multiple binding lists based on the same EntitySet with overlapping or disjoint sets of entities. You can use different queries (or even from the same query, but with different parameters) to get a different set of entities in each binding list.
- Deletion of entities from the context will result in the deleted entity being removed from all binding lists that contain it. The principle behind this is that the binding list is a window into the "current state of affairs" in the state manager. Even if we don't know if a new entity belongs into a binding list, we do know when it doesn't belong anymore, because it has been deleted.
- A binding list has its own Add() method you can use. If you could get a reference to one of our binding lists you could “manually” add new entities to the binding list, and the entity you add will also be added to the context automatically, the same as if it had invoked AddObject() on the context.
All these facts, especially #5, warrant me to give you a couple of tips:
- If you are interested in getting a reference to the binding list directly (i.e. to use its Add method), you can do something like this: var bindingList = ((IListSource)query).GetList() as IBindingList; There is a more convenient ToBindingList() extension method included in the EFExtensions you may want to take a look at.
- If you are not interested in getting the reference to the binding list for yourself, and you are using databinding directly against a query as in the sample at the top of this post, you should know that WinForms will call IListSource.GetList() twice, causing the query to be executed on the server twice! The recommendation then is to bind to the results, rather than the query, using code similar to this: grid.DataSource = someQuery.Execute(MergeOption.AppendOnly); In this case, since the resulting ObjectResult acts as a foward-only, enumerate-once cursor, its implementation of IListSource.GetList() is different from the one in the query: the call will consume the results (i.e. it will iterate through the whole results) and will cache the binding list in the ObjectResult instance. All subsequent calls to IListSource.GetList() will return the same binding list.
We have a few ideas around things we may improve in future versions. Some of them you may be able to guess from this explanation. But I will save that for another post...
Comments
Anonymous
October 08, 2008
PingBack from http://www.easycoded.com/quick-tips-for-entity-framework-databinding/Anonymous
November 02, 2009
Thanks for this interesting post. I have some problems with data binding and WPF. I bind two DataGrids (WPF Toolkit) to the same EntityCollection but changes in one DataGrid aren’t updated in the other one (e.g. add new item). I believe WPF doesn’t use the IListSource.GetList() method. Anyway, it would be nice to see the EntityCollection implementing the INotifyCollectionChanged interface in EF 4.0.Anonymous
November 02, 2009
Hello, We don't have plans to implement INotifyCollectionChanged in 4.0. But this is a good suggestion. Now that the interface has been moved to System.dll we can implement it without having to add a dependency on WindowsBase.dll. In the meanwhile, you can consider using your own ObservableCollections as binding lists for EF. Take a look at Beth Massi's implementation here: http://blogs.msdn.com/bethmassi/archive/2009/05/08/using-the-wpf-observablecollection-with-ef-entities.aspx Or at C# version here: http://blogs.msdn.com/diego/archive/2009/07/26/wpf-databinding-with-the-poco-template.aspx The only detail that those samples are lacking is that they are not synchronizing the binding list with the source when changes happen in the source. You can listen to the AssociationChanged event on the EntityCollection for that. Hope this helps, DiegoAnonymous
November 03, 2009
Hello Diego, We use a similar approach as Beth Massi. However, for RAD (Rapid Application Development) it would be nice to bind directly to the EntityCollections generated by the EDM. I hope to see this in a future release of EF. Thanks, jbeAnonymous
February 26, 2010
I have tried the bindinglist as you have specified and I have to say that you point #5 seems to be incorrect. As I read it, you are saying that adding an Entity to the IBindingList will also result in the Entity being added to the context. This is not happening. Did I misunderstand you?Anonymous
April 10, 2010
Randall, Sorry I did not see your comment before. Yes, IBindingList.Add will result in the entity beging added to the context. The only case I can think of in which this is not going to happen is for the binding list of a query with MergeOption = NoTracking. Any chances that is what you are doing?Anonymous
February 08, 2011
Hi Divega, what are the best practices about binding self-tracking entities to a WinForms DataGridView control? More explicitly, I have a few of questions as follows:
- Since TrackableCollection cannot be directly bound to the DataSource of a DataGridView, calling ToList() on a TrackableCollection and bind the result to a DataGridView seems the only way. Is this correct?
- How is the Add/Delete/Modify operation happening on the list (from q#1) reflected into underlining TrackableCollection? Especially in the case of deletion.
- Any advice on how to use self-tracking entities with WinForms/WPF databinding? I hardly found quality answer on the net, so can you give me some hints? Thanks!
- Anonymous
July 18, 2011
Hi all, Im stuck up with an issue in ma application .I have created an edmx file for temporary data storage,approx 10-12 entities have been mapped to database tables .Now ,I have deleted 1 table from the database but still the application is working fine with the created entity .Can anyone tell me about its behavior. Thanks in advance.