Associação de dados com WPF
Importante
Este documento é válido apenas para o WPF no .NET Framework
Este documento descreve a associação de dados para o WPF no .NET Framework. Para novos projetos do .NET Core, 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 associar tipos POCO a controles do 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 associar os tipos definidos no modelo aos controles do WPF. A estrutura de associação de dados do WPF permite a navegação entre objetos relacionados: selecionar linhas no modo de exibição mestre faz com que a exibição de detalhes seja atualizada com os dados filho 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.
Usar a opção "Object" para criar fontes de dados do WPF
Com a versão anterior do Entity Framework, é recomendável usar a opção Banco de dados ao criar uma nova fonte de dados com base em um modelo criado com o Designer EF. 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 Designers de EF para Visual Studio 2012 e Visual Studio 2013 geram um contexto que deriva de DbContext junto com classes de entidade POCO simples. Com o Visual Studio 2010, recomendamos a troca por 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 Objeto ao criar uma nova fonte de dados, conforme mostrado neste passo a passo.
Se necessário, você pode reverter para a geração de código baseada em ObjectContext para modelos criados com o Designer EF.
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, confira Como instalar o NuGet.
Criar o aplicativo
- Abra o Visual Studio
- Arquivo -> Novo -> Projeto….
- Selecione Windows no painel esquerdo e WPFApplication no painel direito
- Insira 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
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.
Definir um modelo
Neste passo a passo, você pode optar por implementar um modelo usando o Code First ou o Designer do EF. Conclua 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 modelo de um banco de dados usando o designer do EF
Ao usar o desenvolvimento do 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 mouse no nome do projeto
- Selecione Adicionar e, em seguida, Novo item
- Selecione Classe e insira o Produto para o nome da classe
- Substitua a definição da classe Product 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; }
}
}
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.
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> informam ao contexto 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, que inclui preencher objetos com dados de um banco de dados, fazer controle de alterações e persistir dados para o 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 modelo 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 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 acesso.
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 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, você precisará selecionar o Microsoft SQL Server como a 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
Selecione OK e você será perguntado se deseja criar um novo banco de dados, selecione Sim
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
Realizar engenharia reversa do modelo
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 ADO.NET
Insira 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
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
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 você exibir no Designer do Entity Framework. 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 do 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 do 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
Atualizar a geração de código para associaçã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 associação de dados usando o WPF, é desejável usar ObservableCollection para propriedades de coleção para que o WPF possa acompanhar as alterações feitas nas coleções. Para esse fim, modificaremos os modelos para usar ObservableCollection.
Abra o Gerenciador de Soluções e localize o arquivo ProductModel.edmx
Localize o arquivo ProductModel.tt que estará 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 "ObservableCollection". Elas estão localizadas aproximadamente nas linhas 296 e 484.
Localize e substitua a primeira ocorrência de "HashSet" por "ObservableCollection". Essa 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". Isso está localizado aproximadamente na linha 424.
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 ObservableCollection<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 evirtuais (Overridable no Visual Basic) e sua classe não deve ser selada (NotOverridable no Visual Basic). Ao usar as propriedades de navegação do 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 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 escolher um tipo de fonte de dados, selecione Objeto e clique em Avançar
Na caixa de diálogo Selecionar os 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 CategoriaClique em Concluir.
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 janelas-> Fontes de Dados
Pressione o ícone de fixação para que a janela Fontes de Dados não seja ocultada automaticamente. Talvez seja necessário apertar o botão atualizar se a janela já estiver visível.
Selecione a fonte de dados Categoria e arraste-a no formulário.
O seguinte aconteceu quando arrastamos esta fonte:
- O recurso categoryViewSource e o controle categoryDataGrid foram adicionados ao XAML
- A propriedade DataContext no elemento de grade pai foi definida como "{StaticResource categoryViewSource }". O recurso categoryViewSource serve como origem da associação para o elemento de grade externo\pai. Os elementos de grade internos herdam o valor DataContext da grade pai (a propriedade ItemsSource da categoryDataGrid é definida como "{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 grade 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 no formulário.
- O recurso categoryProductsViewSource e a grade productDataGrid são adicionados ao XAML
- O caminho de associação para esse recurso é definido como Products
- A estrutura de associação de dados do WPF garante que somente produtos relacionados à categoria selecionada sejam exibidos em 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:
Adicionar código que manipula a interação de dados
É hora de adicionar alguns manipuladores de eventos à janela principal.
Na janela XAML, clique no elemento <Janela, que seleciona a janela principal
Na janela Propriedades, escolha Eventos na parte superior direita e clique duas vezes na caixa de texto à direita do rótulo Carregado
Adicione também o evento Clique para o botão Salvar clicando duas vezes no botão Salvar no designer.
Isso o leva ao código por trás do formulário; agora editaremos o código para usar o ProductContext para executar o acesso a dados. Atualize o código do MainWindow, 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 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.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 o 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 produto na grade inferior Não insira nada nas colunas de ID, pois a chave primária é gerada pelo banco de dados
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.
Recursos adicionais
Para saber mais sobre a associação de dados a coleções usando o WPF, confira este tópico na documentação do WPF.