Compartilhar via


Associação de dados com WinForms

Este guia passo a passo mostra como associar tipos POCO a controles do Windows Forms (Formulários do Windows) em um formulário de "mestre-detalhe". 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\mestre) e Produto (dependente\detalhe). Em seguida, as ferramentas do Visual Studio são usadas para associar os tipos definidos no modelo aos controles WinForms. A estrutura de associação de dados do WinForms permite a navegação entre objetos relacionados: selecionar linhas na visão mestre faz com que a visão de detalhes seja atualizada com os dados dos filhos correspondentes.

As capturas de tela e as listagens de código neste passo a passo são obtidas do Visual Studio 2013, mas você pode concluir este passo a passo com o Visual Studio 2012 ou o Visual Studio 2010.

Pré-Requisitos

Você precisa ter o Visual Studio 2013, Visual Studio 2012 ou Visual Studio 2010 instalados 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 Como instalar o NuGet.

Criar o aplicativo

  • Abra o Visual Studio
  • Arquivo –> Novo –> Projeto....
  • Selecione Windows no painel esquerdo e Windows FormsApplication no painel direito
  • Insira 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, também é adicionada uma referência a System.ComponentModel.DataAnnotations. 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 associação de dados bidirecional com a classificação ao usar o Windows Forms. Para fazer isso, estenderemos ObservableCollection para adicionar a funcionalidade IListSource.

  • Adicione uma classe ObservableListSource ao projeto:
    • Clique com o botão direito do mouse no nome do projeto
    • Selecionar Adicionar –> Novo Item
    • Selecione Classe e insira ObservableListSource para o nome da classe
  • Substitua o código gerado por padrão pelo seguinte código:

Essa classe habilita a associaçã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 pelo ToBindingList dá 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 Designer EF. Conclua uma das duas seções a seguir.

Opção 1: Definir um modelo usando o Código Primeiro

Esta seção mostra como criar um modelo e seu banco de dados associado usando o Code First. Vá para a próxima seção (Opção 2: Definir um modelo usando Database First) se preferir usar o Database First para fazer engenharia reversa do modelo de um banco de dados usando o Designer do EF

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 pelo 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 pelo 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 as propriedades DbSet<TEntity> . 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, incluindo preencher objetos com dados de um banco de dados, rastreamento 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 pelo 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 Banco de Dados Primeiro

Esta seção mostra como usar o Database First para fazer engenharia reversa do modelo de um banco de dados usando o designer EF. Se você concluiu a seção anterior (Opção 1: Definir um modelo usando o 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á alvejando um banco de dados existente, ele já estará criado, mas para este passo a passo, precisamos criar um banco de dados para que possamos acessar.

O servidor de banco de dados 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.

  • Exibição –> Gerenciador de Servidores

  • Clique com o botão direito do mouse em Conexões de Dados –> Adicionar Conexão...

  • Se você não tiver se conectado a um banco de dados do Gerenciador de Servidores antes de precisar selecionar o Microsoft SQL Server como a fonte de dados

    Alterar fonte de dados

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

    Adicionar conexão LocalDB

    Adicionar Connection Express

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

    Criar banco de dados

  • Agora, o novo banco de dados será exibido no Gerenciador de Servidores, clique com o botão direito do mouse nele e selecione Nova Consulta

  • Copie o SQL a seguir para a nova consulta e 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 à esquerda e, em seguida, Modelo de Dados de Entidade do ADO.NET

  • Insira ProductModel como o nome e clique em OK

  • Isso inicia o Assistente de Modelo de Dados de Entidade

  • Selecione Gerar do Banco de Dados e clique em Avançar

    Escolher Conteúdo do Modelo

  • Selecione a conexão com o banco de dados que você criou na primeira seção, insira ProductContext como o nome da cadeia de conexão e clique em Avançar

    Escolha sua conexão

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

    Escolha seus objetos

Depois que o processo de engenharia reversa for concluído, o novo modelo será adicionado ao seu projeto e aberto para você visualizar no Entity Framework Designer. Um arquivo App.config também foi adicionado ao seu projeto com os detalhes de conexão do banco de dados.

Etapas adicionais no Visual Studio 2010

Se você estiver trabalhando no Visual Studio 2010, precisará atualizar o designer de EF para usar a geração de código EF6.

  • Clique com o botão direito do mouse em um local vazio do seu modelo no Designer EF e selecione Adicionar Item de Geração de Código...
  • Selecione Modelos Online no menu à esquerda e pesquise por DbContext
  • Selecione o Gerador DbContext EF 6.x para C#, insira ProductsModel como o nome e clique em Adicionar

Atualizando a geração de código para associação de dados

O EF gera código a partir do modelo usando modelos T4. Os modelos fornecidos com o Visual Studio ou baixados da galeria do Visual Studio destinam-se ao uso de finalidade geral. Isso significa que as entidades geradas a partir desses modelos têm propriedades ICollection<T> simples. No entanto, ao fazer a associação de dados, é desejável ter propriedades de coleção que implementem iListSource. É por isso que criamos a classe ObservableListSource acima e agora vamos modificar os modelos para usar essa 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

    Modelo de Produto

  • 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". Elas estão localizadas em aproximadamente as linhas 296 e 484.

  • Localize e substitua a primeira ocorrência de "HashSet" por "ObservableListSource". Essa 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 das entidades seja regenerado. Se o código não for regenerado automaticamente, clique com o botão direito do mouse em ProductModel.tt e escolha "Executar Ferramenta Personalizada".

Se agora você abrir o arquivo Category.cs (que está aninhado em ProductModel.tt), deverá 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 uma 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 o carregamento lento criando instâncias de tipos de proxy derivados durante o runtime e substituindo as propriedades virtuais em suas classes para adicionar o gancho de carregamento. Para obter o carregamento lento de objetos relacionados, você deve declarar os getters de propriedade de navegação como públicos e virtuais (substituíveis no Visual Basic) e a classe não deve ser lacrada (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, escolhemos tornar as propriedades de navegação virtuais pelo mesmo motivo

Associar objeto a controles

Adicione as classes definidas no modelo como fontes de dados para este aplicativo WinForms.

  • No menu principal, selecione Project –> 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 objetos de dados, desdobre o WinFormswithEFSample duas vezes e selecione Categoria Não há necessidade de selecionar a fonte de dados Produto, pois chegaremos a ela por meio da propriedade do Produto na fonte de dados Categoria.

    Fonte de dados

  • Clique em Concluir. Se a janela Fontes de Dados não estiver aparecendo, selecione Exibição –> Outras Fontes de> Dados do Windows

  • Clique no ícone de fixação, para que a janela Fontes de Dados não se oculte automaticamente. Talvez seja necessário apertar o botão atualizar se a janela já estiver visível.

    Fonte de Dados 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 Categoria e arraste-a no formulário. Por padrão, um novo DataGridView (categoryDataGridView) e controles da barra de ferramentas de navegação são adicionados ao designer. Esses controles estão associados aos componentes BindingSource (categoryBindingSource) e Binding Navigator (categoryBindingNavigator) que também são criados.

  • Edite as colunas no 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 a partir da fonte de dados Categoria e arraste 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 de salvarmos 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 a componentes BindingSource no designer. Na próxima seção, adicionaremos código ao código por trás para definir categoryBindingSource.DataSource para a coleção de entidades que atualmente são controladas por DbContext. Quando arrastamos e soltamos os Produtos de baixo da Categoria, o WinForms cuidou 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 selecionada no momento serão exibidos no productDataGridView.

  • Habilite o botão Salvar na barra de ferramentas de Navegação clicando no botão direito do mouse e selecionando Habilitado.

    Designer de Formulário 1

  • Adicione o manipulador de eventos para o botão Salvar clicando duas vezes no botão. Isso adicionará o manipulador de eventos e o levará ao código por trás do formulário. O código para o manipulador de eventos do 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 da janela do formulário principal, conforme mostrado abaixo.

O código declara uma instância de execução longa do ProductContext. O objeto ProductContext é usado para consultar e salvar dados no banco de dados. O método Dispose() na instância ProductContext é chamado 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.

    Formulário 1 antes de salvar

  • Depois de salvar, as chaves geradas pela loja são mostradas na tela.

    Formulário 1 após salvar

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

    Gerenciador de Objetos do Servidor