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