Databinding with WinForms (Enlace de datos con WinForms)
En este tutorial paso a paso se muestra cómo enlazar tipos POCO a controles de Windows Forms (WinForms) en un formulario "maestro-detalle". La aplicación usa Entity Framework para rellenar objetos con datos de la base de datos, realizar un seguimiento de los cambios y conservar los datos en la base de datos.
El modelo define dos tipos que participan en una relación uno a varios: Category (principal\master) y Product (dependent\detail). A continuación, Visual Studio herramientas se usan para enlazar los tipos definidos en el modelo a los controles de WinForms. El marco de enlace de datos de WinForms permite la navegación entre objetos relacionados: la selección de filas en la vista maestra hace que la vista de detalles se actualice con los datos secundarios correspondientes.
Las capturas de pantalla y las listas de código de este tutorial se toman de Visual Studio 2013 pero puede completar este tutorial con Visual Studio 2012 o Visual Studio 2010.
Requisitos previos
Debe tener instalado Visual Studio 2013, Visual Studio 2012 o Visual Studio 2010 para completar este tutorial.
Si usa Visual Studio 2010, también tiene que instalar NuGet. Para obtener más información, vea Installing NuGet.
Crear la aplicación
- Apertura de Visual Studio
- Archivo - Nuevo > : Project......
- Seleccione Windows en el panel izquierdo y Windows FormsApplication en el panel derecho.
- Escriba WinFormswithEFSample como nombre.
- Seleccione Aceptar.
Instalación del Entity Framework NuGet paquete
- En Explorador de soluciones, haga clic con el botón derecho en el proyecto WinFormswithEFSample.
- Seleccione Administrar NuGet paquetes...
- En el cuadro de diálogo Administrar NuGet paquetes, seleccione la pestaña En línea y elija el paquete EntityFramework.
- Haz clic en Instalar
Nota
Además del ensamblado EntityFramework, también se agrega una referencia a System.ComponentModel.DataAnnotations. Si el proyecto tiene una referencia a System.Data.Entity, se quitará cuando se instale el paquete EntityFramework. El ensamblado System.Data.Entity ya no se usa para Entity Framework 6 aplicaciones.
Implementación de IListSource para colecciones
Las propiedades de colección deben implementar la interfaz IListSource para habilitar el enlace de datos de dos vías con ordenación al usar Windows Forms. Para ello, vamos a ampliar ObservableCollection para agregar la funcionalidad IListSource.
- Agregue una clase ObservableListSource al proyecto:
- Haga clic con el botón derecho en el nombre del proyecto.
- Seleccione Agregar - Nuevo elemento.
- Seleccione Clase y escriba ObservableListSource como nombre de clase.
- Reemplace el código generado de forma predeterminada por el código siguiente:
Esta clase habilita el enlace de datos de dos vías, así como la ordenación. La clase se deriva de ObservableCollection < T y agrega una implementación explícita de > IListSource. El método GetList() de IListSource se implementa para devolver una implementación de IBindingList que permanece sincronizada con ObservableCollection. La implementación de IBindingList generada por ToBindingList admite la ordenación. El método de extensión ToBindingList se define en el ensamblado EntityFramework.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Data.Entity;
namespace WinFormswithEFSample
{
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());
}
}
}
Definición de un modelo
En este tutorial puede optar por implementar un modelo mediante Code First o EF Designer. Complete una de las dos secciones siguientes.
Opción 1: Definir un modelo mediante Code First
En esta sección se muestra cómo crear un modelo y su base de datos asociada mediante Code First. Vaya a la sección siguiente ( Opción2: Definir un modelo mediante Database First) si prefiere usar Database First para realizar ingeniería inversa del modelo desde una base de datos mediante el diseñador de EF.
Al usar Code First desarrollo, normalmente empieza por escribir .NET Framework clases que definen el modelo conceptual (dominio).
- Agregar una nueva clase Product al proyecto
- Reemplace el código generado de forma predeterminada por el código siguiente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormswithEFSample
{
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
}
- Agregue una clase Category al proyecto.
- Reemplace el código generado de forma predeterminada por el código siguiente:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormswithEFSample
{
public class Category
{
private readonly ObservableListSource<Product> _products =
new ObservableListSource<Product>();
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ObservableListSource<Product> Products { get { return _products; } }
}
}
Además de definir entidades, debe definir una clase que derive de DbContext y exponga las propiedades de DbSet TEntity. Las propiedades DbSet permiten al contexto saber qué tipos desea incluir en el modelo. Los tipos DbContext y DbSet se definen en el ensamblado EntityFramework.
Una instancia del tipo derivado de DbContext administra los objetos de entidad durante el tiempo de ejecución, lo que incluye rellenar los objetos con datos de una base de datos, el seguimiento de cambios y la persistencia de datos en la base de datos.
- Agregue una nueva clase ProductContext al proyecto.
- Reemplace el código generado de forma predeterminada por el código siguiente:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
namespace WinFormswithEFSample
{
public class ProductContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
}
Compile el proyecto.
Opción 2: Definir un modelo mediante Database First
En esta sección se muestra cómo usar Database First ingeniería inversa del modelo desde una base de datos mediante ef designer. Si completó la sección anterior (Opción1:Definir un modelo mediante Code First) , omita esta sección y vaya directamente a la sección Carga diferida.
Crear una base de datos existente
Normalmente, cuando tiene como destino una base de datos existente, ya se creará, pero para este tutorial es necesario crear una base de datos a la que acceder.
El servidor de base de datos que se instala con Visual Studio es diferente en función de la versión de Visual Studio que haya instalado:
- Si usa Visual Studio 2010, va a crear una base de datos SQL Express.
- Si usa Visual Studio 2012, va a crear una base de LocalDB datos.
Vamos a continuar y a generar la base de datos.
Vista: Explorador de servidores
Haga clic con el botón derecho en Conexiones de datos- Agregar conexión...
Si no se ha conectado a una base de datos desde Explorador de servidores antes de tener que seleccionar Microsoft SQL Server como origen de datos.

Conectar a LocalDB o SQL Express, en función de cuál haya instalado, y escriba Products como nombre de la base de datos.


Seleccione Aceptar y se le preguntará si desea crear una nueva base de datos, seleccione Sí.

La nueva base de datos aparecerá ahora en Explorador de servidores, haga clic con el botón derecho en ella y seleccione Nueva consulta.
Copie la siguiente SQL en la nueva consulta, haga clic con el botón derecho en la consulta y seleccione Ejecutar.
CREATE TABLE [dbo].[Categories] (
[CategoryId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Categories] PRIMARY KEY ([CategoryId])
)
CREATE TABLE [dbo].[Products] (
[ProductId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
[CategoryId] [int] NOT NULL,
CONSTRAINT [PK_dbo.Products] PRIMARY KEY ([ProductId])
)
CREATE INDEX [IX_CategoryId] ON [dbo].[Products]([CategoryId])
ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_dbo.Products_dbo.Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([CategoryId]) ON DELETE CASCADE
Modelo de ingeniería inversa
Vamos a hacer uso de Entity Framework Designer, que se incluye como parte de Visual Studio, para crear nuestro modelo.
Project : Agregar nuevo elemento...
Seleccione Datos en el menú de la izquierda y, a continuación, ADO.NET Entity Data Model
Escriba ProductModel como nombre y haga clic en Aceptar.
Se inicia el Asistente para Entity Data Model aplicaciones.
Seleccione Generar a partir de la base de datos y haga clic en Siguiente.

Seleccione la conexión a la base de datos que creó en la primera sección, escriba ProductContext como nombre de la cadena de conexión y haga clic en Siguiente.

Haga clic en la casilla situada junto a "Tablas" para importar todas las tablas y haga clic en "Finalizar".

Una vez completado el proceso de ingeniería inversa, el nuevo modelo se agrega al proyecto y se abre para que se vea en el Entity Framework Designer. También App.config archivo de conexión al proyecto con los detalles de conexión de la base de datos.
Pasos adicionales de Visual Studio 2010
Si está trabajando en Visual Studio 2010, deberá actualizar ef designer para usar la generación de código EF6.
- Haga clic con el botón derecho en un punto vacío del modelo en EF Designer y seleccione Agregar elemento de generación de código...
- Seleccione Plantillas en línea en el menú de la izquierda y busque DbContext.
- Seleccione el generador de DbContext de EF 6.x para C#, escriba ProductsModel como nombre y haga clic en Agregar.
Actualización de la generación de código para el enlace de datos
EF genera código a partir del modelo mediante plantillas T4. Las plantillas que se suministran Visual Studio o se descargan desde la galería Visual Studio están diseñadas para uso general. Esto significa que las entidades generadas a partir de estas plantillas tienen propiedades ICollection < T > sencillas. Sin embargo, al realizar el enlace de datos, es conveniente tener propiedades de colección que implementen IListSource. Este es el motivo por el que creamos la clase ObservableListSource anterior y ahora vamos a modificar las plantillas para usar esta clase.
Abra el Explorador de soluciones y busque el archivo ProductModel.edmx.
Busque el ProductModel.tt que se anidará en el archivo ProductModel.edmx.

Haga doble clic en el ProductModel.tt archivo para abrirlo en el editor de Visual Studio.
Busque y reemplace las dos apariciones de "ICollection" por "ObservableListSource". Se encuentran aproximadamente en las líneas 296 y 484.
Busque y reemplace la primera aparición de "HashSet" por "ObservableListSource". Esta repetición se encuentra aproximadamente en la línea 50. No reemplace la segunda aparición de HashSet que se encuentra más adelante en el código.
Guarde el ProductModel.tt archivo. Esto debería hacer que el código de las entidades se vuelva a generar. Si el código no se vuelve a generar automáticamente, haga clic con el botón derecho en ProductModel.tt y elija "Ejecutar herramienta personalizada".
Si ahora abre el archivo Category.cs (que está anidado en ProductModel.tt), debería ver que la colección Products tiene el tipo ObservableListSource Product >.
Compile el proyecto.
Carga diferida
La propiedad Products de la clase Category y la propiedad Category de la clase Product son propiedades de navegación. En Entity Framework, las propiedades de navegación proporcionan una manera de navegar por una relación entre dos tipos de entidad.
EF ofrece la opción de cargar entidades relacionadas desde la base de datos automáticamente la primera vez que accede a la propiedad de navegación. Con este tipo de carga (denominado carga diferida), tenga en cuenta que la primera vez que se accede a cada propiedad de navegación se ejecutará una consulta independiente en la base de datos si el contenido no está ya en el contexto.
Cuando se usan tipos de entidad POCO, EF logra la carga diferida mediante la creación de instancias de tipos de proxy derivados durante el tiempo de ejecución y, a continuación, la invalidación de las propiedades virtuales en las clases para agregar el enlace de carga. Para obtener la carga diferida de objetos relacionados, debe declarar los getters de propiedad de navegación como públicos y virtuales (reemplazables en Visual Basic) y la clase no debe estar sellada(NotOverible en Visual Basic). Al usar Database First de navegación se convierten automáticamente en virtuales para habilitar la carga diferida. En la Code First de navegación, hemos elegido convertir las propiedades de navegación en virtuales por la misma razón.
Enlace de objetos a controles
Agregue las clases que se definen en el modelo como orígenes de datos para esta aplicación WinForms.
En el menú principal, seleccione Project - Agregar nuevo origen de datos ... (en Visual Studio 2010, debe seleccionar Datos - Agregar nuevo origen de datos...)
En la ventana Elegir un tipo de origen de datos, seleccione Objeto y haga clic en Siguiente.
En el cuadro de diálogo Seleccionar los objetos de datos, desensenreda winFormswithEFSample dos veces y seleccione Categoría No es necesario seleccionar el origen de datos Product, ya que llegaremos a él a través de la propiedad Product del origen de datos Category.

Haga clic en Finalizar. Si la ventana Orígenes de datos no aparece, seleccione Ver - Otros Windows- Orígenes de datos
Presione el icono de anclar para que la ventana Orígenes de datos no se oculte automáticamente. Es posible que tenga que hacer clic en el botón actualizar si la ventana ya estaba visible.

En Explorador de soluciones, haga doble clic en el archivo Form1.cs para abrir el formulario principal en el diseñador.
Seleccione el origen de datos Category (Categoría) y arrástrelo al formulario. De forma predeterminada, se agregan al diseñador nuevos controles DataGridView(categoryDataGridView)y barra de herramientas de navegación. Estos controles están enlazados a los componentes BindingSource (categoryBindingSource) y Binding Navigator (categoryBindingNavigator) que también se crean.
Edite las columnas de la categoríaDataGridView. Queremos establecer la columna CategoryId en solo lectura. La base de datos genera el valor de la propiedad CategoryId después de guardar los datos.
- Haga clic con el botón derecho en el control DataGridView y seleccione Editar columnas...
- Seleccione la columna CategoryId y establezca ReadOnly en True.
- Presione Aceptar.
Seleccione Products (Productos) en el origen de datos Category (Categoría) y arrástrelo al formulario. ProductDataGridView y productBindingSource se agregan al formulario.
Edite las columnas en productDataGridView. Queremos ocultar las columnas CategoryId y Category y establecer ProductId en solo lectura. La base de datos genera el valor de la propiedad ProductId después de guardar los datos.
- Haga clic con el botón derecho en el control DataGridView y seleccione Editar columnas....
- Seleccione la columna ProductId y establezca ReadOnly en True.
- Seleccione la columna CategoryId y presione el botón Quitar. Haga lo mismo con la columna Categoría .
- Haga clic en Aceptar.
Hasta ahora, asociamos nuestros controles DataGridView con componentes BindingSource en el diseñador. En la sección siguiente agregaremos código al código subyacente para establecer categoryBindingSource.DataSource en la colección de entidades de las que DbContext realiza el seguimiento actualmente. Cuando arrastramos y quitamos Products desde en category, WinForms se encargaba de configurar la propiedad productsBindingSource.DataSource en categoryBindingSource y productsBindingSource.DataMember en Products. Debido a este enlace, solo se mostrarán en productDataGridView los productos que pertenecen a la categoría seleccionada actualmente.
Habilite el botón Guardar en la barra de herramientas navegación haciendo clic en el botón derecho del mouse y seleccionando Habilitado.

Agregue el controlador de eventos para el botón Guardar haciendo doble clic en el botón. Esto agregará el controlador de eventos y le llevará al código subyacente del formulario. El código del controlador categoryBindingNavigatorSaveItem_Click eventos se agregará en la sección siguiente.
Agregar el código que controla la interacción de datos
Ahora agregaremos el código para usar ProductContext para realizar el acceso a los datos. Actualice el código de la ventana del formulario principal como se muestra a continuación.
El código declara una instancia de ejecución larga de ProductContext. El objeto ProductContext se usa para consultar y guardar datos en la base de datos. A continuación, se llama al método Dispose() de la instancia de ProductContext desde el método OnClosing invalidado. Los comentarios de código proporcionan detalles sobre lo que hace el código.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.Entity;
namespace WinFormswithEFSample
{
public partial class Form1 : Form
{
ProductContext _context;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_context = new ProductContext();
// 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();
}
private void 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();
this.productsDataGridView.Refresh();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this._context.Dispose();
}
}
}
Probar la aplicación Windows Forms
Compile y ejecute la aplicación y puede probar la funcionalidad.

Después de guardar las claves generadas en el almacén, se muestran en la pantalla.

Si ha usado Code First, también verá que se crea automáticamente una base de datos WinFormswithEFSample.ProductContext.
