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

    Change Data Source

  • Conecte-se ao LocalDB ou ao SQL Express, dependendo de qual deles você instalou, e insira Produtos como o nome do banco de dados

    Add Connection LocalDB

    Add Connection Express

  • Selecione OK e você será perguntado se deseja criar um novo banco de dados, selecione Sim

    Create Database

  • 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

    Choose Model Contents

  • 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

    Choose Your Connection

  • Clique na caixa de seleção ao lado de 'Tabelas' para importar todas as tabelas e clique em 'Concluir'

    Choose Your Objects

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

    Product Model Template

  • 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.

    Data Source

  • 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.

    Data Source 2

  • 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.

    Form 1 Designer

  • 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.

    Form 1 Before Save

  • Depois de salvar as chaves geradas do repositório, elas são mostradas na tela.

    Form 1 After Save

  • Se você usou Code First, também verá que um banco de dados WinFormswithEFSample.ProductContext foi criado para você.

    Server Object Explorer