Поделиться через


Data Binding with DbContext


The information in this post is out of date.

Visit msdn.com/data/ef for the latest information on current and past releases of EF.

For WPF Data Binding see https://msdn.com/data/jj574514

For WinForms Data Binding see https://msdn.com/data/jj682076


 

 

We recently announced the release of Feature CTP4 for the ADO.Net Entity Framework (EF). CTP4 contains a preview of new features that we are considering adding to the core framework and would like community feedback on. CTP4 builds on top of the existing Entity Framework 4 (EF4) which shipped as part of .NET Framework 4.0 and Visual Studio 2010.

CTP4 contains the first preview of a set of Productivity Improvements for EF that provide a cleaner and simpler API surface designed to allow developers to accomplish the same tasks with less code and fewer concepts. The Productivity Improvement effort is described in more detail in a recent Design Blog post.

Since the release of CTP4 we have been working to enable more scenarios for developers using DbContext & DbSet, one of the scenarios we have been looking at is Data Binding in WPF and WinForms. The purpose of this post is to share the direction we are heading and provide the chance for feedback on our design. The features discussed in this post are not included in CTP4.

Requirements

Before we cover the current design let’s take a quick look at the requirements we used to drive the design:

The high level requirement is to “enable two way data binding to a set of objects retrieved from a DbContext”

  • The set of objects bound to a control will all be of the same base type
    (i.e. all belong to the same DbSet<T>)
  • Binding should display Unchanged, Modified and Added objects of the given type
    • Deleted objects should not be displayed
  • Data binding features in WPF and WinForms (such as sorting) should be supported natively
  • Data binding should be two-way
    • When the UI adds an object to the data source it should be added to the DbContext
    • When the UI removes an object from the data source it should deleted from the DbContext
    • When a new object enters the DbContext it should appear in the UI
    • When an object is deleted from the DbContext it should be removed from the UI

WPF Experience

Let’s say we have a WPF window that displays a list of Employees and has a “Save” button:

 

Using some new additions to the DbSet<T> API we could implement the code behind our window as follows:

namespace DatabindingSample
{
    public partial class CustomerWindow : Window
    {
        private EmployeeContext context = new EmployeeContext();

        public CustomerWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        { 
            context.Employees.Load(); 
            this.DataContext = context.Employees.Local; 
        }

        private void Save_Click(object sender, RoutedEventArgs e)
        {
            this.context.SaveChanges();
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            this.context.Dispose();
        }
    }
}

DbSet<TEntity>.Local

This new property will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet. As new objects enter the DbSet (through queries, DbSet.Add/Attach, etc.) they will appear in the ObservableCollection. When an object is deleted from the DbSet it will also be removed from the ObservableCollection. Adding or Removing from the ObservableCollection will also perform the corresponding Add/Remove on the DbSet. Because WPF natively supports binding to an ObservableCollection there is no additional code required to have two way data binding with full support for WPF sorting, filtering etc.

Load Extension Method

Because DbSet<TEntity>.Local gives us objects that are currently tracked by the DbContext we need a way to get existing objects from the database loaded into memory. Load is a new extension method on IQueryable that will cause the results of the query to be iterated, in EF this equates to materializing the results as objects and adding them to the DbContext in the Unchanged state. Of course you aren’t restricted to using the Load method to bring data into memory for databinding, any existing operations that cause objects to be materialized will also work (such as iterating results, ToList, ToArray, etc.).

Filtering

Because Load is an extension on IQueryable it can be called directly on a DbSet to load all items into memory or it can be used after LINQ operators. For example we could re-write our WPF code to only load Employees with an IsActive flag set to true:

private void Window_Loaded(object sender, RoutedEventArgs e) 

     context.Employees.Where(e => e.IsActive).Load(); 
     this.DataContext = context.Employees.Local; 
}

The above example only works if we only ever bring active Employees into the context, but if other parts of the application need to bring inactive Employees into memory using the same context then we need a better way to filter. If you are using WPF then this functionality is already built in:

private void Window_Loaded(object sender, RoutedEventArgs e) 

     context.Employees.Load(); 

     var view = new ListCollectionView(context.Employees.Local);
     view.Filter = e => ((Employee)e).IsActive;
     this.DataContext =  view;
}

Outside of WPF there are many other databinding frameworks (such as Continuous LINQ) that solve this problem.

WinForms Experience

The WinForms experience is very similar to WPF except that we need a collection that implements IBindingList rather than an ObservableCollection. We unfortunately can’t cater for both technologies with the same collection type because WPF will use the IBindingList interface if it is present and this gives a degraded databinding experience. Therefore we require one additional step to convert our ObservableCollection<TEntity> to something that WinForms will happily bind to. Using a similar form as the WPF example but this time in WinForms:

 

Our code behind looks very similar to the WPF code except that we call ToBindingList to convert our local list to a WinForms friendly equivalent:

namespace DatabindingSample
{
    public partial class CustomerForm : Form
    {
        private EmployeeContext context = new EmployeeContext();
 
        public CustomerForm()
        {
            InitializeComponent();
        }

        private void CustomerForm_Load(object sender, EventArgs e)
        {
            context.Employees.Load(); 
            this.employeeBindingSource.DataSource = context.Employees.Local.ToBindingList();
        }

        private void saveButton_Click(object sender, EventArgs e)
        {
            this.context.SaveChanges();
        }

        private void CustomerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.context.Dispose();
        }
    }
}

Binding to Queries

One obvious question might be “Why not just bind directly to the results of a query?”. There are a couple of reasons we decided not to pursue this approach. LINQ to Entities queries pull results directly from the database which means they do not include objects that are in the context in an added state and may also include objects that have been marked as deleted, this is obviously not ideal when databinding as the UI won’t display new objects and may show deleted objects. Binding directly to a query would also cause a query to be issued to the database every time the UI iterates the collection, meaning if the UI needed to refresh it would result in all your data being pulled from the database again.

Summary

In this post we covered our plans to support databinding to the contents of a DbSet<T>, including existing data that is loaded from the database and new objects that haven’t been persisted to the database yet. We saw that two way databinding is supported in both WPF and WinForms. We also covered the new Load extension method that is used to materialize the results of a LINQ query into object instances prior to databinding. We’d like to hear any feedback you have on the proposed patterns or API surface.

Rowan Miller
Program Manager
ADO.Net Entity Framework Team

Comments

  • Anonymous
    September 08, 2010
    I didn´t understood the difference from put context.Employees.ToList() at a Property and use it.

  • Anonymous
    September 08, 2010
    Will the result of DbSet<TEntity>.Local be a thread-save Observable Collection? Or, to be more precisely, do you also support a LoadAsync() method?

  • Anonymous
    September 08, 2010
    The comment has been removed

  • Anonymous
    September 09, 2010
    We use silverlight and WCF RIA services.  Are you guys working with that team in order to make these changes transparent to the client side of our code?

  • Anonymous
    September 09, 2010
    @Felipe Fujiy Binding to context.Employees.ToList() would just give you a one-time read-only binding because ToList() gives you a list that is completely isolated from the underlying context. If you added an Employee through the data grid you would have to write additional code to also add it to the context so that it would be persisted to the database. Also because the IQueryable part of context.Employees represents a database query you would be subject to the limitations discussed in the “Binding to Queries” section of the post. @Daniel Auth EF in general is not thread safe, because the ObservableCollection interacts with the underlying context as items are added/removed it should only be used in the same thread as the context it was created from. @tobi Binding directly to a context certainly isn’t going to satisfy those of us who would rather abstract out the data access layer using repositories, UoW, etc. (and we’ve built DbContext and friends to support those patterns too). The Productivity Improvement work is aimed a wide range of developers though and binding directly to a local context is acceptable in a lot of applications. @Paul We are working with the RIA Services team to add support for DbContext so that you would have the same client side experience using either ObjectContext or DbContext.

  • Anonymous
    September 09, 2010
    Rowan Miller, I guess you are right that if reliability and quality do not count (and I acknowledge that they sometimes don't) this can actually be useful. I wonder how quickly memory consumption goes up because all entites ever loaded are kept in memory.

  • Anonymous
    September 09, 2010
    Will the DbSet.Local collection be notified from changes in other DbContexts as well? The scenario I have in mind is when you got one window showing all Employees and you open multiple windows to do detail changes on different employees. If you use the same DbContext in all theese windows then you will not be able to just call DbContext.SaveChanges on the master DbContext without persisting every change that may be ongoing in the other windows, right? Creating new DbContext for all child windows will work as separate transactions in this case and would allow only the changes done to one Employee and its sub objects to be persisted without affecting changes that are done to other context that may later be canceled. Is this a scenario that you're trying to solve with this up and comming release or will we still have to jump through hoops to support such a use case? Overall I've been tinkering with the CTP4 drop for a while and I like where the Code First experience is heading

  • Anonymous
    September 17, 2010
    @tobi I see that I missed your main point, that having a single context serving the entire application is probably not a good idea. I totally agree, the sample used MainForm/Window only because the sample application I wrote only had a single form. We’d recommend using the same patterns you would with any UnitOfWork and scoping it to appropriate level. I’ve updated the post to rename the window/form classes to avoid any confusion. @Dennis Heij Changes are only reflected in the context that you make them in. The scenario you describe is interesting but it’s not something we are trying to solve at this stage.

  • Anonymous
    September 29, 2010
    Can you post a downloadable sample?

  • Anonymous
    September 29, 2010
    Right now we have to subclass ObservableCollection to implement 2-way updates, so simplifying this would be a great addition. If you also provide a way to simply refreshes when data in another context is saved to the database that would be great.

  • Anonymous
    October 02, 2010
    Hi, Is there a tentaive date when this feature is getting released. How is the intimation about this release beong planned to be communicated to all interested users

  • Anonymous
    October 04, 2010
    This is great stuff guys.  RAD style features such as this are a real timesaver.  It's also really helpful for beginners since it's doing the majority of the plumbing work.

  • Anonymous
    November 29, 2010
    This is very relevant for what I am working on, and will be a welcome feature.   Also, I often need to bind a grid to a child collection (navigation property), would the Local() method be available on child collections as well? For example, this.DataContext = context.Employees.Where(e => e.Name == "Sam").References.Local;

  • Anonymous
    December 04, 2010
    Apologies for the slow follow up… @Scott & @Venkatesh This feature will be available in EF Feature CTP5 which will be out in the next week or so. @Alex D We did look at doing something along these lines but it seems like the best solution is just to use ObservableCollection within your object, you can still type Employee.References as an ICollection but if you can initialize it as an ObservableCollection then both WPF and EF are happy.

  • Anonymous
    June 29, 2011
    It's clear how to load additional entities into the context. How can we remove entities from the context that have been deleted from the database? In the above example, say that some other method created a context, deleted some entities, and called SaveChanges(). How would you update context.Employees.Local so that the DataContext no longer contained those deleted entities?

  • Anonymous
    December 19, 2011
    This all works great for editable collections of entities where the underlying list does not need to be thrown away and re-loaded. It would be nice to have a flush or abandon on the DbSet/DbContext so that it could be cleared of all entities and then re-loaded to reflect changes that have occured in the underlying data base.

  • Anonymous
    April 10, 2012
    Is there are a way to turn this off (like invoking AsNoTracking)? I want to do a table join and project only selected columns into a read-only anonymous type to save the overhead of loading unnecessary data (like binary which I would like to see an example of delay loaded), but I get the "Data binding directly to a store query is not supported" exception.

  • Anonymous
    April 10, 2012
    You can ignore my last post. I just needed call ToArray, I did read the post which mentioned this but am used to using AsEnumerable in Linq to Sql to load objects into memory but for some reason doesn't seem to work in this situation and couldn't find the Load extension method.

  • Anonymous
    July 04, 2012
    Any example on how to do filtering with WinForms?

  • Anonymous
    June 14, 2015
    If I want to bind the context.People to the listbox as data source should I use BindingSource or just a BindingList inbetween?