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ê.
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de