Freigeben über


EF Feature CTP5: Code First and WinForms Databinding

 


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 Windows Forms Data Binding see https://msdn.com/data/jj682076


 

In December we released ADO.NET Entity Framework Feature Community Technology Preview 5 (CTP5). In addition to the Code First approach this CTP also contains a preview of a new API that provides a more productive surface for working with the Entity Framework. This API is based on the DbContext class and can be used with the Code First, Database First, and Model First approaches.

This post provides an introduction to creating your model using Code First development and then using the types defined in the model as data sources in the “master-detail” Windows Forms (WinForms) application.

In this walkthrough, the model defines two types that participate in one-to-many relationship: Category (principal\master) and Product (dependent\detail). Then, the Visual Studio tools are used to bind the types defined in the model to the WinForm controls. The WinForm data-binding facilities enable navigation between related objects:selecting rows in the master view causes the detail view to update with the corresponding child data. Note, that the data-binding process does not depend on what approach is used to define the model (Code First, Database First, or Model First).

In this walkthrough the default code-first conventions are used to map your .NET types to a database schema and create the database the first time the application runs. You can override the default code-first conventions by using Data Annotations or the Code First Fluent API. For more information see: EF Feature CTP5: Code First Walkthrough (section 9 - Data Annotations) and EF Feature CTP5: Fluent API Samples

 

Install EF CTP5

1. If you haven’t already done so then you need to install Entity Framework Feature CTP5.

 

Create a solution and a class library project to which the model will be added

1. Open Visual Studio 2010.

2. From the menu, select File -> New -> Project… .

3. Select “Visual C#” from the left menu and then select “Class Library” template.

4. Enter CodeFirstModel as the project name and CodeFirstWithWinForms as the solution name. Note, to be able to specify different names for the project and the solution names must check the “Create directory for solution” option (located on the right bottom corner of the New Project dialog).

5. Select OK.

 

 Create a simple model

When using the code-first development you usually begin by writing .NET classes that define your domain model. The classes do not need to derive from any base classes or implement any interfaces. In this section you will define your model using C# code.

1. Remove the default source code file that was added to the CodeFirstModel project (Class1.cs).

2. Add reference to the EntityFramework assembly. To add a reference do:

    1.1. Press the right mouse button on the CodeFirstModel project, select Add Reference…. .

    1.2. Select the “.NET” tab.

    1.3. Select EntityFramework from the list.

    1.4. Click OK.

3. Add a new class to the CodeFirstModel. Enter Category for the class name.

4. Implement the Category class as follows:

Note: The Products property is of ObservableListSource<T> type. If we just wanted to facilitate two-way data binding in Windows Forms we could have made the property of the BindingList<T> type. But that would not support sorting. The ObservableListSource<T> class enables sorting. This class will be implemented and explained later in this walkthrough.

 

using System.ComponentModel;

public class Category

{

    public int CategoryId { get; set; }

    public string Name { get; set; }

    public virtual ObservableListSource<Product> Products { get { return _products; } }

    private readonly ObservableListSource<Product> _products =

new ObservableListSource<Product>();

}

5. Add another new class to the project. Enter Product for the class name. Replace the Product class definition with the code below.

 publicclassProduct

{

    publicint ProductId { get; set; }

    publicstring Name { get; set; }

    publicvirtualCategory Category { get; set; }

    publicint CategoryId { get; set; }

}

The Products property on the Category class and Category property on the Product class are navigation properties. Navigation properties in the Entity Framework provide a way to navigate an association\relationship between two entity types, returning either a reference to an object, if the multiplicity is either one or zero-or-one, or a collection if the multiplicity is many. 

The Entity Framework gives you an option of loading related entities from the database automatically whenever you access a navigation property. With this type of loading (called lazy loading), be aware that each navigation property that you access results in a separate query executing against the database if the entity is not already in the context.

When using POCO entity types, lazy loading is achieved by creating instances of derived proxy types during runtime and then overriding virtual properties to add the loading hook. To get lazy loading of related objects, you must declare navigation property getters as public, virtual (Overridable in Visual Basic), and not sealed (NotOverridable in Visual Basic). In the code above, the Category.Products and Product.Category navigation properties are virtual.

6. Add a new class called ObservableListSource to the project. This class enables two-way data binding as well as sorting. The class extends ObservableCollection<T> and adds an explicit implementation of IListSource. The GetList() method of IListSource is implemented to return an IBindingList implementation that stays in sync with the ObservableCollection. The IBindingList implementation generated by ToBindingList supports sorting.

Implement the ObservableListSource <T> class as follows: 

using System.Collections;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.ComponentModel;

using System.Data.Entity;

using System.Diagnostics.CodeAnalysis;

 

public class ObservableListSource<T> : ObservableCollection<T>, IListSource

    where T : class

{

    private IBindingList _bindingList;

 

    bool IListSource.ContainsListCollection

    {

        get { return false; }

    }

 

    IList IListSource.GetList()

    {

        return _bindingList ?? (_bindingList = this.ToBindingList());

    }

}

 Create a derived context

In this step we will define a context that derives from System.Data.Entity.DbContext and exposes a DbSet<TEntity> for each class in the model. The context class manages the entity objects during runtime, which includes retrieval of objects from a database, change tracking, and persistence to the database. A DbSet<TEntity> represents the collection of all entities in the context of a given type. 

1. Add a new class to the CodeFirstModel. Enter ProductContext for the class name.

2.  Implement the class definition as follows:

 using System.Data.Entity;

using System.Data.Entity.Database;

using System.Data.Entity.Infrastructure;

publicclassProductContext : DbContext

{

    publicDbSet<Category> Categories { get; set; }

    publicDbSet<Product> Products { get; set; }

}

3. Build the project.

In the code above we use a “convention over configuration” approach. When using this approach you rely on common mapping conventions instead of explicitly configuring the mapping. For example, if a property on a class contains “ID” or “Id” string, or the class name followed by Id (Id can be any combination of upper case and lower case) the Entity Framework will treat these properties as primary keys by convention. This approach will work in most common database mapping scenarios, but the Entity Framework provides ways for you to override these conventions. For example, if you explicitly want to set a property to be a primary key, you can use the [Key] data annotation. For more information about mapping conventions, see the following blog: Conventions for Code-First.

 

 Create a Windows Forms application

In this step we will add a new Windows Forms application to the CodeFirstWithWinForms solution.

1. Add a new Windows Forms application to the CodeFirstWithWinForms solution.

     1.1. Press the right mouse button on the CodeFirstWithWinForms solution and select Add -> New Project… .

    1.2. Select “Windows Forms Application” template. Leave the default name (WindowsFormsApplication1).

    1.3. Click OK.

2. Add reference to the CodeFirstModel class library project. That is where our model and the object context are defined.

     1.1. Press the right mouse button on the project and select Add Reference… .

    1.2. Select the “Projects” tab.

    1.3. Select CodeFirstModel from the list.

    1.4. Click OK.

3. Add reference to the EntityFramework assembly.

4. Add the classes that are defined in the model as data sources for this Windows Forms application.

    1.1. From the main menu, select Data -> Add New Data Sources… .

    1.2. Select Objects and click Next.

    1.3. In the “What objects do you want to bind to” list, select Category. There no need to select the Product data source, because we can get to it through the Product’s property on the Category data source.

    1.4. Click Finish.

 

5. Show the data sources (from the main menu, select Data -> Show Data Sources). By default the Data Sources panel is added on the left of the Visual Studio designer.

 

6. Select the Data Sources tab and press the pin icon, so the window does not auto hide. You may need to hit the refresh button if the window was already visible.

 

7. Select the Category data source and drag it on the form. By default, a new DataGridView (categoryDataGridView) and Navigation toolbar controls are added to the designer. These controls are bound to the BindingSource (categoryBindingSource) and Binding Navigator (categoryBindingNavigator) components that were created as well.

 

8. Edit the columns on the categoryDataGridView. We want to set the CategoryId column to read-only. The value for the CategoryId property is generated by the database after we save the data.

  1.1. Click the right mouse button on the DataGridView control and select Edit Columns… .

  1.2. Select the CategoryId column and set ReadOnly to True.

9. Select Products from under the Category data source and drag it on the form. The productDataGridView and productBindingSource are added to the form.

10. Edit the columns on the productDataGridView. We want to hide the CategoryId and Category columns and set ProductId to read-only. The value for the ProductId property is generated by the database after we save the data.

    1.1.Click the right mouse button on the DataGridView control and select Edit Columns… .

    1.2.Select the ProductId column and set ReadOnly to True.

    1.3.Select the CategoryId column and press the Remove button. Do the same with the Category column.

So far, we associated our DataGridView controls with BindingSource components in the designer. In the next section we will add code to the code behind to set categoryBindingSource.DataSource to the collection of entities that are currently tracked by DbContext. When we dragged-and-dropped Products from under the Category, the WinForms took care of setting up the productsBindingSource.DataSource property to categoryBindingSource and productsBindingSource.DataMember property to Products. Because of this binding, only the products that belong to the currently selected Category will be displayed in the productDataGridView.

11. Enable the Save button on the Navigation toolbar by clicking the right mouse button and selecting Enabled.

 

 

12. Add the event handler for the save button by double-clicking on the button. This will add the event handler and bring you to the code behind for the form. The code for the categoryBindingNavigatorSaveItem_Click event handler will be added in the next section.

Add the code that handles data interaction

1. Implement the code behind class (Form1.cs) as follows. The code comments explain what the code does.

 using System.Data.Entity;

using CodeFirstModel;

using System.Data.Entity.Database;

 

publicpartialclassForm1 : Form

{

    ProductContext _context;

    public Form1()

    {

        InitializeComponent();

    }

 

    protectedoverridevoid OnLoad(EventArgs e)

    {

        base.OnLoad(e);

        _context = newProductContext();

 

        // Call the Load method to get the data for the given DbSet from the database.

        // The data is materialized as entities. The entities are managed by

        // the DbContext instance.

        _context.Categories.Load();

 

        // Bind the categoryBindingSource.DataSource to

        // all the Unchanged, Modified and Added Category objects that

        // are currently tracked by the DbContext.

        // Note that we need to call ToBindingList() on the ObservableCollection<TEntity>

        // returned by the DbSet.Local property to get the BindingList<T>

        // in order to facilitate two-way binding in WinForms.

        this.categoryBindingSource.DataSource =

            _context.Categories.Local.ToBindingList();

    }

 

    privatevoid categoryBindingNavigatorSaveItem_Click(object sender, EventArgs e)

    {

        this.Validate();

 

        // Currently, the Entity Framework doesn’t mark the entities

        // that are removed from a navigation property (in our example the Products) as deleted in the context.

        // The following code uses LINQ to Objects against the Local collection

        // to find all products and marks any that do not have a Category reference as deleted.

        // The ToList call is required because otherwise the collection will be modified

   // by the Remove call while it is being enumerated.

        // In most other situations you can do LINQ to Objects directly against the Local property without using ToList first.

        foreach (var product in _context.Products.Local.ToList())

        {

            if (product.Category == null)

            {

                _context.Products.Remove(product);

            }

        }

         // Save the changes to the database.

        this._context.SaveChanges();

 

        // Refresh the controls to show the values

        // that were generated by the database.

        this.categoryDataGridView.Refresh();

    }

 

    protectedoverridevoid OnClosing(CancelEventArgs e)

    {

        base.OnClosing(e);

 

        this._context.Dispose();

    }

}

Test the application

When you run the application the first time, the Entity Framework uses the default conventions to create the database on the localhost\SQLEXPRESS instance and names it after the fully qualified type name of the derived context (CodeFirstModel.ProductContext). The subsequent times, unless the model changes, the existing database will be used. You can change the default behavior by overriding the code-first default conventions. For more information, see EF Feature CTP5: Code First Walkthrough.

1. Set the WindowsFormsApplication1 project as a startup project.

    1.1.  Click the right mouse button on the WindowsFormsApplication1 project and select “Set as StartUp project”.

2. Compile and run the application.

3. Enter a category name in the top grid and product names in the bottom grid.

 

4. Press the Save button to save the data to the database. After the call to DbContext’s SaveChanges(), the CategoryId and ProductId properties are populated with the database generated values.

 

 

Summary

In this post we demonstrated how to create a model using Code First development and then use the types defined in the model as data sources in the “master-detail” Windows Form application.

 

Julia Kornich

Programming Writer

Comments

  • Anonymous
    February 16, 2011
    I missed something, or it was told to implement ObservableListSource<T>, but it wasn't used in any place?

  • Anonymous
    February 16, 2011
    Thanks for this great article. I'd like to see the same article for WPF Databinding  

  • Anonymous
    February 20, 2011
    Where is ToBindingList method implementation?

  • Anonymous
    February 20, 2011
    Hi Julia! Thisarticle it's really helpfull. Thanks. Local.ToBindingList() is an Extension of ObservableCollection<T> defined in: using System.Data.Entity; But, where is Local.ToList() method implementation?

  • Anonymous
    February 22, 2011
    I found. Local.ToList() method implementation is defined with: using System.Linq;

  • Anonymous
    February 22, 2011
    Hi how can the  validation attributes to data classes be shown automatically on the Form i need to define the validation attribute once and they should be displayed automatically on the UI (Details view or Grdview ) like silverlight DataForm  

  • Anonymous
    February 27, 2011
    Love the code first features. Seems like the most "real-world" application yet. any idea when CTP5 will be RTM? I would like to use this at work but, getting buy-in for a CTP feature is next to impossible. Thanks!

  • Anonymous
    April 12, 2011
    The comment has been removed

  • Anonymous
    September 27, 2011
    Awful lot of work compared to Code First with MVC.  

  • Anonymous
    March 24, 2012
    very usefull for me

  • Anonymous
    February 24, 2013
    can you add the actual code that was used for this article.

  • Anonymous
    February 25, 2013
    @harryperales - The info in this post is out of date. There is a step by step walkthrough (including detailed code listings) here msdn.microsoft.com/.../jj682076