Associação de dados com WinForms
Este passo a passo mostra como vincular tipos POCO a controles de formulários de janela (WinForms) em um formulário de "detalhes mestre". O aplicativo usa o Entity Framework para preencher objetos com dados do banco de dados, controlar alterações e persistir dados no banco de dados.
O modelo define dois tipos que participam da relação um-para-muitos: Categoria (principal\master) e Produto (dependent\detail). Em seguida, as ferramentas do Visual Studio são usadas para vincular os tipos definidos no modelo para os controles WinForms. A estrutura de vinculação de dados WinForms permite a navegação entre objetos relacionados: a seleção de linhas no modo de exibição mestre faz com que o modo de exibição de detalhes seja atualizado com os dados filho correspondentes.
As capturas de tela e listagens de código neste passo a passo são tiradas do Visual Studio 2013, mas você pode concluir este passo a passo com o Visual Studio 2012 ou Visual Studio 2010.
Pré-Requisitos
Você precisa ter o Visual Studio 2013, Visual Studio 2012 ou Visual Studio 2010 instalado para concluir este passo a passo.
Se você estiver usando o Visual Studio 2010, também precisará instalar o NuGet. Para obter mais informações, consulte Instalando o NuGet.
Criar o aplicativo
- Abra o Visual Studio
- Arquivo -> Novo ->Projeto...
- Selecione Windows no painel esquerdo e Windows FormsApplication no painel direito
- Digite WinFormswithEFSample como o nome
- Selecione OK
Instalar o pacote NuGet do Entity Framework
- No Gerenciador de Soluções, clique com o botão direito do mouse no projeto WinFormswithEFSample
- Selecione Gerenciar pacotes NuGet...
- Na caixa de diálogo Gerenciar Pacotes NuGet, selecione a guia Online e escolha o pacote EntityFramework
- Clique em Instalar
Observação
Além do assembly EntityFramework, uma referência a System.ComponentModel.DataAnnotations também é adicionada. Se o projeto tiver uma referência a System.Data.Entity, ele será removido quando o pacote EntityFramework for instalado. O assembly System.Data.Entity não é mais usado para aplicativos do Entity Framework 6.
Implementando IListSource para coleções
As propriedades de coleção devem implementar a interface IListSource para habilitar a vinculação de dados bidirecional com classificação ao usar o Windows Forms. Para fazer isso, vamos estender ObservableCollection para adicionar a funcionalidade IListSource.
- Adicione uma classe ObservableListSource ao projeto:
- Clique com o botão direito do mouse no nome do projeto
- Selecione Adicionar -> Novo Item
- Selecione Class e digite ObservableListSource para o nome da classe
- Substitua o código gerado por padrão com o seguinte código:
Essa classe permite a vinculação de dados bidirecional, bem como a classificação. A classe deriva de ObservableCollection<T> e adiciona uma implementação explícita de IListSource. O método GetList() de IListSource é implementado para retornar uma implementação IBindingList que permanece em sincronia com o ObservableCollection. A implementação IBindingList gerada por ToBindingList oferece suporte à classificação. O método de extensão ToBindingList é definido no assembly 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());
}
}
}
Definir um modelo
Neste passo a passo, você pode optar por implementar um modelo usando o Code First ou o EF Designer. Complete uma das duas seções a seguir.
Opção 1: definir um modelo usando o Code First
Esta seção mostra como criar um modelo e seu banco de dados associado usando Code First. Pule para a próxima seção (Opção 2: definir um modelo usando o Database First) se preferir usar o Database First para fazer engenharia reversa do seu modelo a partir de um banco de dados usando o EF designer
Ao usar o desenvolvimento Code First, você geralmente começa escrevendo classes do .NET Framework que definem seu modelo conceitual (domínio).
- Adicionar uma nova classe Product ao projeto
- Substitua o código gerado por padrão com o seguinte código:
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; }
}
}
- Adicione uma classe Category ao projeto.
- Substitua o código gerado por padrão com o seguinte código:
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; } }
}
}
Além de definir entidades, você precisa definir uma classe que deriva de DbContext e expõe DbSet<TEntity> propriedades. As propriedades DbSet permitem que o contexto saiba quais tipos você deseja incluir no modelo. Os tipos DbContext e DbSet são definidos no assembly EntityFramework.
Uma instância do tipo derivado DbContext gerencia os objetos de entidade durante o tempo de execução, o que inclui preencher objetos com dados de um banco de dados, controle de alterações e persistência de dados no banco de dados.
- Adicione uma nova classe ProductContext ao projeto.
- Substitua o código gerado por padrão com o seguinte código:
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 o projeto.
Opção 2: definir um modelo usando o Database First
Esta seção mostra como usar o Database First para fazer engenharia reversa do modelo a partir de um banco de dados usando o EF designer. Se você concluiu a seção anterior (Opção 1: definir um modelo usando Code First), ignore esta seção e vá direto para a seção Carregamento lento.
Criar um banco de dados existente
Normalmente, quando você está direcionando um banco de dados existente, ele já será criado, mas para este passo a passo, precisamos criar um banco de dados para acessar.
O servidor de banco de dados que é instalado com o Visual Studio é diferente dependendo da versão do Visual Studio que você instalou:
- Se você estiver usando o Visual Studio 2010, criará um banco de dados SQL Express.
- Se você estiver usando o Visual Studio 2012, criará um banco de dados LocalDB.
Vamos em frente e gerar o banco de dados.
Exibir ->Gerenciador de Servidores
Clique com o botão direito do mouse em Conexão de Dados -> Adicionar Conexões...
Se você não tiver se conectado a um banco de dados do Gerenciador de Servidores antes, precisará selecionar o Microsoft SQL Server como a fonte de dados
Conecte-se ao LocalDB ou ao SQL Express, dependendo de qual deles você instalou, e insira Produtos como o nome do banco de dados
Selecione OK e você será perguntado se deseja criar um novo banco de dados, selecione Sim
O novo banco de dados agora aparecerá no Gerenciador de Servidores, clique com o botão direito do mouse nele e selecione Nova Consulta
Copie o seguinte SQL para a nova consulta, clique com o botão direito do mouse na consulta e selecione Executar
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 Engenharia Reversa
Vamos usar o Entity Framework Designer, que está incluído como parte do Visual Studio, para criar nosso modelo.
Projeto -> Adicionar novo item...
Selecione Dados no menu esquerdo e Modelo de Dados de Entidade ADO.NET
Digite ProductModel como o nome e clique em OK
Isso inicia o Assistente do Modelo de Dados de Entidade
Selecione Gerar do banco de dados e clique em Avançar
Selecione a conexão com o banco de dados criado na primeira seção, digite ProductContext como o nome da cadeia de conexão e clique em Next
Clique na caixa de seleção ao lado de 'Tabelas' para importar todas as tabelas e clique em 'Concluir'
Depois que o processo de engenharia reversa for concluído, o novo modelo será adicionado ao seu projeto e aberto para exibição no Entity Framework Designer. Um arquivo App.config também foi adicionado ao seu projeto com os detalhes de conexão para o banco de dados.
Etapas adicionais no Visual Studio 2010
Se você estiver trabalhando no Visual Studio 2010, precisará atualizar o designer EF para usar a geração de código EF6.
- Clique com o botão direito do mouse em um ponto vazio do seu modelo no EF Designer e selecione Adicionar item de geração de código...
- Selecione Modelos Online no menu à esquerda e procure por DbContext
- Selecione o EF 6.x DbContext Generator para C#, digite ProductsModel como o nome e clique em Adicionar
Atualizando a geração de código para vinculação de dados
O EF gera código do seu modelo usando modelos T4. Os modelos fornecidos com o Visual Studio ou baixados da galeria do Visual Studio destinam-se ao uso geral. Isso significa que as entidades geradas a partir desses modelos têm propriedades ICollection<T> simples. No entanto, ao fazer a vinculação de dados, é desejável ter propriedades de coleta que implementam IListSource. É por isso que criamos a classe ObservableListSource acima e agora vamos modificar os modelos para fazer uso dessa classe.
Abra o Gerenciador de Soluções e localize o arquivo ProductModel.edmx
Localize o arquivo ProductModel.tt que será aninhado no arquivo ProductModel.edmx
Clique duas vezes no arquivo ProductModel.tt para abri-lo no editor do Visual Studio
Localize e substitua as duas ocorrências de "ICollection" por "ObservableListSource". Estes estão localizados em aproximadamente as linhas 296 e 484.
Localize e substitua a primeira ocorrência de "HashSet" por "ObservableListSource". Esta ocorrência está localizada aproximadamente na linha 50. Não substitua a segunda ocorrência de HashSet encontrada posteriormente no código.
Salve o arquivo ProductModel.tt. Isso deve fazer com que o código para entidades seja regenerado. Se o código não regenerar automaticamente, clique com o botão direito do mouse em ProductModel.tt e escolha "Executar ferramenta personalizada".
Se você abrir o arquivo Category.cs (que está aninhado em ProductModel.tt), verá que a coleção Products tem o tipo ObservableListSource<Product>.
Compile o projeto.
Carregamento lento
A propriedade Products na classe Category e a propriedade Category na classe Product são propriedades de navegação. No Entity Framework, as propriedades de navegação fornecem uma maneira de navegar em uma relação entre dois tipos de entidade.
O EF oferece a opção de carregar entidades relacionadas do banco de dados automaticamente na primeira vez que você acessar a propriedade de navegação. Com esse tipo de carregamento (chamado de carregamento lento), lembre-se de que, na primeira vez que você acessar cada propriedade de navegação, uma consulta separada será executada no banco de dados se o conteúdo ainda não estiver no contexto.
Ao usar tipos de entidade POCO, o EF obtém carregamento lento criando instâncias de tipos de proxy derivados durante o tempo de execução e, em seguida, substituindo propriedades virtuais em suas classes para adicionar o gancho de carregamento. Para obter carregamento lento de objetos relacionados, você deve declarar getters de propriedade de navegação como public e virtual (Overridable no Visual Basic), e sua classe não deve ser selada (NotOverridable no Visual Basic). Ao usar o Database First, as propriedades de navegação são automaticamente tornadas virtuais para habilitar o carregamento lento. Na seção Code First, optamos por tornar as propriedades de navegação virtuais pelo mesmo motivo
Vincular objeto a controles
Adicione as classes definidas no modelo como fontes de dados para esse aplicativo WinForms.
No menu principal, selecione Projeto -> Adicionar Nova Fonte de Dados... (no Visual Studio 2010, você precisa selecionar Dados -> Adicionar nova fonte de dados...)
Na janela Escolher um Tipo de Fonte de Dados, selecione Objeto e clique em Avançar
Na caixa de diálogo Selecionar os Objetos de Dados, faça unfold do WinFormswithEFSample duas vezes e selecione Categoria Não há necessidade de selecionar a fonte de dados do Produto, pois chegaremos a ela por meio da propriedade do Produto na fonte de dados Categoria.
Clique em Concluir. Se a janela Fontes de Dados não estiver aparecendo, selecione Exibir -> Outras Fontes de Dados do > Windows
Pressione o ícone de alfinete para que a janela Fontes de Dados não oculte automaticamente. Talvez seja necessário pressionar o botão de atualização se a janela já estiver visível.
No Gerenciador de Soluções, clique duas vezes no arquivo Form1.cs para abrir o formulário principal no designer.
Selecione a fonte de dados Category e arraste-a para o formulário. Por padrão, um novo DataGridView (categoryDataGridView) e controles da barra de ferramentas Navegação são adicionados ao designer. Esses controles são vinculados aos componentes BindingSource (categoryBindingSource) e BindingNavigator (categoryBindingNavigator) que também são criados.
Edite as colunas na categoryDataGridView. Queremos definir a coluna CategoryId como somente leitura. O valor da propriedade CategoryId é gerado pelo banco de dados depois que salvamos os dados.
- Clique com o botão direito do mouse no controle DataGridView e selecione Editar colunas...
- Selecione a coluna CategoryId e defina ReadOnly como True
- Pressione OK
Selecione Produtos na fonte de dados Categoria e arraste-a para o formulário. O productDataGridView e productBindingSource são adicionados ao formulário.
Edite as colunas no productDataGridView. Queremos ocultar as colunas CategoryId e Category e definir ProductId como somente leitura. O valor da propriedade ProductId é gerado pelo banco de dados depois que salvamos os dados.
- Clique com o botão direito do mouse no controle DataGridView e selecione Editar colunas....
- Selecione a coluna ProductId e defina ReadOnly como True.
- Selecione a coluna CategoryId e pressione o botão Remover. Faça o mesmo com a coluna Categoria.
- Pressione OK.
Até agora, associamos nossos controles DataGridView aos componentes BindingSource no designer. Na próxima seção, adicionaremos código ao code-behind para definir categoryBindingSource.DataSource à coleção de entidades que são atualmente rastreadas por DbContext. Quando arrastamos e soltamos Produtos da Categoria, os WinForms se encarregaram de configurar a propriedade productsBindingSource.DataSource para categoryBindingSource e a propriedade productsBindingSource.DataMember para Products. Devido a essa associação, somente os produtos que pertencem à Categoria atualmente selecionada serão exibidos no productDataGridView.
Habilite o botão Salvar na barra de ferramentas Navegação clicando com o botão direito do mouse e selecionando Habilitado.
Adicione o manipulador de eventos para o botão salvar clicando duas vezes no botão. Isso adicionará o manipulador de eventos e levará você ao code-behind do formulário. O código para o manipulador de eventos categoryBindingNavigatorSaveItem_Click será adicionado na próxima seção.
Adicionar o código que manipula a interação de dados
Agora adicionaremos o código para usar o ProductContext para executar o acesso a dados. Atualize o código para a janela de formulário principal, conforme mostrado abaixo.
O código declara uma instância de longa execução de ProductContext. O objeto ProductContext é usado para consultar e salvar dados no banco de dados. O método Dispose() na instância ProductContext é então chamado a partir do método OnClosing substituído. Os comentários de código fornecem detalhes sobre o que o código faz.
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();
}
}
}
Testar o aplicativo Windows Forms
Compile e execute o aplicativo e você pode testar a funcionalidade.
Depois de salvar as chaves geradas do repositório, elas são mostradas na tela.
Se você usou Code First, também verá que um banco de dados WinFormswithEFSample.ProductContext foi criado para você.