Partilhar via


Vinculação de dados com WPF

Important

Este documento é válido para WPF apenas no .NET Framework

Este documento descreve a vinculação de dados para WPF no .NET Framework. Para novos projetos .NET, recomendamos que você use o EF Core em vez do Entity Framework 6. A documentação para vinculação de dados no EF Core está aqui: Introdução ao WPF.

Este passo a passo mostra como vincular tipos POCO a controles WPF em um formulário "mestre-detalhe". O aplicativo usa as APIs do 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 vincular os tipos definidos no modelo aos controles WPF. A estrutura de vinculação de dados do WPF permite a navegação entre objetos relacionados: a seleção de linhas na exibição mestre faz com que a exibição de detalhes seja atualizada com os dados filho correspondentes.

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

Use a opção 'Object' para criar fontes de dados WPF

Com a versão anterior do Entity Framework, costumávamos recomendar o uso da opção Banco de Dados ao criar uma nova Fonte de Dados com base em um modelo criado com o EF Designer. Isso ocorreu porque o designer geraria um contexto derivado de ObjectContext e classes de entidade derivadas de EntityObject. Usar a opção Banco de dados ajudaria você a escrever o melhor código para interagir com essa superfície de API.

Os EF Designers para Visual Studio 2012 e Visual Studio 2013 geram um contexto que deriva de DbContext juntamente com classes de entidade POCO simples. Com o Visual Studio 2010, recomendamos a troca para um modelo de geração de código que usa DbContext, conforme descrito posteriormente neste passo a passo.

Ao usar a superfície da API DbContext, você deve usar a opção Object ao criar uma nova fonte de dados, conforme mostrado nesta explicação passo a passo.

Se necessário, você pode reverter para a geração de código baseada em ObjectContext para modelos criados com o EF Designer.

Pre-Requisites

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

  • Abrir o Visual Studio
  • Ficheiro -> Novo -> Projeto....
  • Selecione Windows no painel esquerdo e WPFApplication no painel direito
  • Digite WPFwithEFSample 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

    Note

    Além do assembly do 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.

Definir um modelo

Neste passo a passo, você pode optar por implementar um modelo usando o Code First ou o EF Designer. Preencha 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 o Code First. Vá 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 designer EF

Ao usar o desenvolvimento Code First, você geralmente começa escrevendo classes do .NET Framework que definem seu modelo conceitual (domínio).

  • Adicione uma nova classe ao WPFwithEFSample:
    • Clique com o botão direito do rato no nome do projeto
    • Selecione Adicionar e, em seguida, Novo Item
    • Selecione Classe e insira Produto para o nome da classe
  • Substitua a definição de classe de produto pelo seguinte código:
    namespace WPFwithEFSample
    {
        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 com a seguinte definição:
    using System.Collections.ObjectModel;

    namespace WPFwithEFSample
    {
        public class Category
        {
            public Category()
            {
                this.Products = new ObservableCollection<Product>();
            }

            public int CategoryId { get; set; }
            public string Name { get; set; }

            public virtual ObservableCollection<Product> Products { get; private set; }
        }
    }

As propriedades Products na classe Category e 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.

Além de definir entidades, você precisa definir uma classe que deriva de DbContext e expõe as propriedades DbSet<TEntity> . As propriedades DbSet<TEntity> permitem que o contexto saiba quais tipos você deseja incluir no modelo.

Uma instância do tipo derivado DbContext gerencia os objetos de entidade durante o tempo de execução, o que inclui o preenchimento de objetos com dados de um banco de dados, o controle de alterações e a persistência de dados no banco de dados.

  • Adicione uma nova classe ProductContext ao projeto com a seguinte definição:
    using System.Data.Entity;

    namespace WPFwithEFSample
    {
        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 seu modelo a partir de um banco de dados usando o designer do 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 se direciona um banco de dados existente, ele já estará criado, mas para este tutorial precisamos criar um banco de dados para 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.

  • Visualizar -> Explorador de Servidores

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

  • Se você não se conectou a um banco de dados a partir do Gerenciador de Servidores, antes 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 Conexão Express

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

    Criar Base de Dados

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

  • Project -> Adicionar novo item...

  • Selecione Dados no menu esquerdo e, em seguida, ADO.NET Modelo de Dados de Entidade

  • Introduza ProductModel como o nome e clique em OK

  • Isso inicia o Assistente de Modelo de Dados de Entidade

  • Selecione Gerar a partir 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, digite ProductContext como o nome da cadeia de conexão e clique em Avançar

    Escolha a sua ligação

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

    Escolha seus objetos

Quando 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 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 local vazio do seu modelo no EF Designer e selecione Adicionar item de geração de código...
  • Selecione Modelos Online no menu esquerdo 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 a partir 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 usando o WPF, é desejável usar ObservableCollection para propriedades de coleta para que o WPF possa acompanhar as alterações feitas nas coleções. Para este fim, vamos modificar os modelos para usar ObservableCollection.

  • Abra o Gerenciador de Soluções e localize o arquivo ProductModel.edmx

  • Encontre o arquivo ProductModel.tt que será aninhado no arquivo ProductModel.edmx

    Modelo de Produto WPF

  • Clique duas vezes no arquivo ProductModel.tt para abri-lo no editor do Visual Studio

  • Encontre e substitua as duas ocorrências de "ICollection" por "ObservableCollection". Estes estão localizados aproximadamente nas linhas 296 e 484.

  • Encontre e substitua a primeira ocorrência de "HashSet" por "ObservableCollection". Esta ocorrência está localizada aproximadamente na linha 50. Não substitua a segunda ocorrência de HashSet encontrada posteriormente no código.

  • Localize e substitua a única ocorrência de "System.Collections.Generic" por "System.Collections.ObjectModel". Este está localizado aproximadamente na linha 424.

  • 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 rato 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 ObservableCollection<Product>.

Compile o projeto.

Carregamento preguiçoso

As propriedades Products na classe Category e 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), esteja ciente 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 ao criar instâncias de tipos de proxy derivadas durante o tempo de execução e, em seguida, ao substituir as propriedades virtuais nas suas classes, adicionando assim um gancho para o carregamento. Para obter carregamento lento de objetos relacionados, deve-se declarar as propriedades de navegação getter como públicas e virtuais (Overridable em Visual Basic), e a sua classe não deve ser selada (NotOverridable em Visual Basic). Ao usar o Database First, as propriedades de navegação são automaticamente tornadas virtuais para permitir 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 este aplicativo WPF.

  • Clique duas vezes em MainWindow.xaml no Gerenciador de Soluções para abrir o formulário principal

  • 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 Escolha um tipo de fonte de dados, selecione Objeto e clique em Avançar

  • Na caixa de diálogo Selecionar objetos de dados, desdobre o WPFwithEFSample 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

    Selecionar objetos de dados

  • Clica em Terminar.

  • A janela Fontes de Dados é aberta ao lado da janela MainWindow.xaml Se a janela Fontes de Dados não estiver aparecendo, selecione Exibir -> Outras Fontes de Dados do Windows ->

  • Pressione o ícone de pino para que a janela Fontes de dados não se oculte automaticamente. Talvez seja necessário pressionar o botão de atualização se a janela já estiver visível.

    Fontes de Dados

  • Selecione a fonte de dados Categoria e arraste-a para o formulário.

O seguinte aconteceu quando arrastámos esta fonte:

  • O recurso categoryViewSource e o controle categoryDataGrid foram adicionados ao XAML
  • A propriedade DataContext no elemento Grid pai foi definida como "{StaticResource categoryViewSource }". O recurso categoryViewSource serve como uma fonte de ligação para o elemento Grid outer\parent. Os elementos internos do Grid herdam o valor do DataContext do pai Grid (a propriedade ItemsSource do categoryDataGrid é atribuída a "{Binding}")
    <Window.Resources>
        <CollectionViewSource x:Key="categoryViewSource"
                                d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/>
    </Window.Resources>
    <Grid DataContext="{StaticResource categoryViewSource}">
        <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True"
                    ItemsSource="{Binding}" Margin="13,13,43,191"
                    RowDetailsVisibilityMode="VisibleWhenSelected">
            <DataGrid.Columns>
                <DataGridTextColumn x:Name="categoryIdColumn" Binding="{Binding CategoryId}"
                                    Header="Category Id" Width="SizeToHeader"/>
                <DataGridTextColumn x:Name="nameColumn" Binding="{Binding Name}"
                                    Header="Name" Width="SizeToHeader"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

Adicionar uma grelha de detalhes

Agora que temos uma grade para exibir Categorias, vamos adicionar uma grade de detalhes para exibir os Produtos associados.

  • Selecione a propriedade Products na fonte de dados Category e arraste-a para o formulário.
    • O recurso categoryProductsViewSource e a grelha productDataGrid são adicionados ao XAML.
    • O caminho de vinculação para este recurso é definido como Produtos
    • A estrutura de vinculação de dados do WPF garante que apenas os Produtos relacionados à Categoria selecionada apareçam no productDataGrid
  • Na Caixa de Ferramentas, arraste Button para o formulário. Defina a propriedade Name como buttonSave e a propriedade Content como Save.

O formulário deve ser semelhante a este:

Forma de Designer

Adicionar código que lida com interação de dados

É hora de adicionar alguns manipuladores de eventos à janela principal.

  • Na janela XAML, clique no <elemento Window , isso seleciona a janela principal

  • Na janela Propriedades, escolha Eventos no canto superior direito e, em seguida, clique duas vezes na caixa de texto à direita do rótulo Carregado

    Propriedades da janela principal

  • Adicione também o evento Click para o botão Salvar clicando duas vezes no botão Salvar no designer.

Isso leva você ao "code-behind" do formulário, agora vamos editar o código para usar o ProductContext para realizar operações de acesso a dados. Atualize o código para o MainWindow como mostrado abaixo.

O código declara uma instância de execução prolongada de ProductContext. O objeto ProductContext é usado para consultar e salvar dados no banco de dados. O 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.Data.Entity;
    using System.Linq;
    using System.Windows;

    namespace WPFwithEFSample
    {
        public partial class MainWindow : Window
        {
            private ProductContext _context = new ProductContext();
            public MainWindow()
            {
                InitializeComponent();
            }

            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                System.Windows.Data.CollectionViewSource categoryViewSource =
                    ((System.Windows.Data.CollectionViewSource)(this.FindResource("categoryViewSource")));

                // Load is an extension method on IQueryable,
                // defined in the System.Data.Entity namespace.
                // This method enumerates the results of the query,
                // similar to ToList but without creating a list.
                // When used with Linq to Entities this method
                // creates entity objects and adds them to the context.
                _context.Categories.Load();

                // After the data is loaded call the DbSet<T>.Local property
                // to use the DbSet<T> as a binding source.
                categoryViewSource.Source = _context.Categories.Local;
            }

            private void buttonSave_Click(object sender, RoutedEventArgs e)
            {
                // When you delete an object from the related entities collection
                // (in this case Products), the Entity Framework doesn’t mark
                // these child entities as deleted.
                // Instead, it removes the relationship between the parent and the child
                // by setting the parent reference to null.
                // So we manually have to delete the products
                // that have a Category reference set to null.

                // The following code uses LINQ to Objects
                // against the Local collection of Products.
                // 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 use 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);
                    }
                }

                _context.SaveChanges();
                // Refresh the grids so the database generated values show up.
                this.categoryDataGrid.Items.Refresh();
                this.productsDataGrid.Items.Refresh();
            }

            protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
            {
                base.OnClosing(e);
                this._context.Dispose();
            }
        }

    }

Testar o aplicativo WPF

  • Compile e execute o aplicativo. Se você usou Code First, verá que um banco de dados WPFwithEFSample.ProductContext foi criado para você.

  • Insira um nome de categoria na grade superior e nomes de produtos na grade inferior Não insira nada em colunas de ID, porque a chave primária é gerada pelo banco de dados

    Janela principal com novas categorias e produtos

  • Pressione o botão Salvar para salvar os dados no banco de dados

Após a chamada para SaveChanges() do DbContext, as IDs são preenchidas com os valores gerados pelo banco de dados. Como chamamos Refresh() após SaveChanges(), os controles DataGrid também são atualizados com os novos valores.

Janela principal com IDs preenchidos

Recursos adicionais

Para saber mais sobre a vinculação de dados a coleções usando o WPF, consulte este tópico na documentação do WPF.