Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este guia passo a passo mostra como associar tipos POCO aos controlos Windows Forms (WinForms) num formulário "mestre-detalhe". A aplicação utiliza o Entity Framework para preencher objetos com dados da base de dados, acompanhar alterações e persistir dados para a base de dados.
O modelo define dois tipos que participam numa relação um-para-muitos: Categoria (principal/primário) e Produto (dependente/secundário). Depois, as ferramentas do Visual Studio são usadas para associar os tipos definidos no modelo aos controlos do WinForms. O framework de ligação de dados WinForms permite a navegação entre objetos relacionados: selecionar linhas na vista mestre faz com que a vista de detalhe seja atualizada com os dados filhos correspondentes.
As capturas de ecrã e as listas de código neste guia são retiradas do Visual Studio 2013, mas pode completar esta guia com o Visual Studio 2012 ou Visual Studio 2010.
Pre-Requisites
É necessário ter instalado o Visual Studio 2013, Visual Studio 2012 ou Visual Studio 2010 para completar este guia.
Se estiveres a usar Visual Studio 2010, também tens de instalar o NuGet. Para mais informações, consulte Instalação do NuGet.
Criar o aplicativo
- Abrir o Visual Studio
- Ficheiro -> Novo -> Projeto....
- Selecione Windows no painel esquerdo e Windows FormsApplication no painel direito
- Entra WinFormswithEFSample como nome
- Selecione OK
Instalar o pacote NuGet do Entity Framework
- No Explorador de Soluções, clique com o botão direito no projeto WinFormswithEFSample
- Selecionar Gerir Pacotes do NuGet...
- No diálogo Gestão de Pacotes NuGet, selecione o separador Online e escolha o pacote EntityFramework
- Clique em Instalar
Observação
Para 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, esta será removida quando o pacote EntityFramework for instalado. O assembly System.Data.Entity já não é utilizado para aplicações do Entity Framework 6.
Implementação do IListSource para Coleções
As propriedades da coleção devem implementar a interface IListSource para permitir a ligação de dados bidirecional com ordenação ao utilizar o Windows Forms. Para isso, vamos estender o ObservableCollection para adicionar funcionalidade do IListSource.
- Adicione uma classe ObservableListSource ao projeto:
- Clique com o botão direito no nome do projeto
- Selecionar Adicionar -> Novo Item
- Selecione Classe e introduza ObservableListSource para o nome da classe
- Substitua o código gerado por defeito pelo seguinte:
Esta classe permite a ligação de dados bidirecional, bem como a ordenação. A classe deriva de ObservableCollection<T> e adiciona uma implementação explícita de IListSource. O método GetList() do IListSource é implementado para devolver uma implementação IBindingList que se mantém sincronizada com a ObservableCollection. A implementação IBindingList gerada pelo ToBindingList suporta ordenação. O método de extensão ToBindingList está 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 guia pode optar por implementar um modelo usando o Code First ou o EF Designer. Complete uma das duas secções seguintes.
Opção 1: Definir um Modelo usando o Code First
Esta secção mostra como criar um modelo e a sua base de dados associada usando o Code First. Salta para a secção seguinte (Opção 2: Define um modelo usando o Database First) se preferires usar o Database First para fazer engenharia inversa do teu modelo a partir de uma base de dados usando o designer EF
Ao usar o desenvolvimento do Code First, normalmente começa por escrever classes do .NET Framework que definem o seu modelo conceptual (de domínio).
- Adicionar uma nova classe de Produto ao projeto
- Substitua o código gerado por defeito pelo seguinte:
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 de Categoria ao projeto.
- Substitua o código gerado por defeito pelo seguinte:
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; } }
}
}
Para além de definir entidades, é necessário definir uma classe que derive do DbContext e exponha propriedades de DbSet<TEntity> . As propriedades DbSet permitem ao contexto saber quais os tipos que quer incluir no modelo. Os tipos DbContext e DbSet são definidos na assembly EntityFramework.
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.
- Substitua o código gerado por defeito pelo seguinte:
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; }
}
}
Compilar o projeto.
Opção 2: Definir um modelo usando o Database First
Esta secção mostra como usar o Database First para fazer engenharia inversa do seu modelo a partir de uma base de dados usando o designer EF. Se completaste a secção anterior (Opção 1: Definir um modelo usando Code First), depois salta esta secção e vai diretamente para a secção de Carregamento Preguiçoso .
Criar uma Base de Dados Existente
Normalmente, quando se está a apontar para uma base de dados existente, ela já estará criada, mas para este tutorial precisamos de criar uma base de dados para podermos aceder a ela.
O servidor de base de dados instalado com o Visual Studio varia consoante a versão do Visual Studio que tem instalado:
- Se estiveres a usar Visual Studio 2010, vais criar uma base de dados SQL Express.
- Se estiveres a usar Visual Studio 2012, então vais criar uma base de dados LocalDB .
Vamos avançar e gerar a base de dados.
Visualização -> Explorador de Servidores
Clique com o botão direito em Ligações de Dados -> Adicionar Ligação...
Se nunca se ligou a uma base de dados do Server Explorer, terá de selecionar Microsoft SQL Server como fonte de dados
Ligue-se ao LocalDB ou SQL Express, dependendo de qual tem instalado, e introduza Produtos como nome da base de dados
Selecione OK e ser-lhe-á perguntado se quer criar uma nova base de dados, selecione Sim
A nova base de dados irá agora aparecer no Explorador de Servidores, clique com o botão direito nela e selecione Nova Consulta
Copie o SQL seguinte para a nova consulta, depois clique com o botão direito 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 utilizar o Entity Framework Designer, que está incluído no Visual Studio, para criar o nosso modelo.
Projeto -> Adicionar Novo Item...
Selecione Dados no menu esquerdo e depois Modelo de Dados de Entidade ADO.NET
Introduza ProductModel como nome e clique em OK
Isto inicia o Assistente do Modelo de Dados de Entidade
Selecione Gerar a partir da base de dados e clique em Seguinte
Selecione a ligação à base de dados que criou na primeira secção, introduza ProductContext como nome da cadeia de ligação e clique em Seguinte
Clique na caixa de seleção ao lado de 'Tabelas' para importar todas as tabelas e clique em 'Terminar'
Assim que o processo de engenharia reversa termina, o novo modelo é adicionado ao seu projeto e aberto para que possa visualizar no Entity Framework Designer. Um ficheiro App.config também foi adicionado ao seu projeto com os detalhes de ligação da base de dados.
Passos adicionais no Visual Studio 2010
Se estiveres a trabalhar no Visual Studio 2010, então terás de atualizar o designer do EF para usar geração de código EF6.
- Clique com o botão direito num espaço 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 Gerador EF 6.x DbContext para C#, introduza ProductsModel como nome e clique em Adicionar
Atualização da geração de código para ligação de dados
O EF gera código a partir do teu modelo usando templates T4. Os modelos fornecidos com o Visual Studio ou descarregados da galeria Visual Studio destinam-se a uso geral. Isto significa que as entidades geradas a partir destes modelos têm propriedades simples de ICollection<T> . No entanto, ao fazer ligação de dados, é desejável ter propriedades de recolha que implementem o IListSource. É por isso que criámos a classe ObservableListSource acima e agora vamos modificar os templates para tirar partido desta classe.
Abra o Explorador de Soluções e encontre o ficheiro ProductModel.edmx
Encontre o ficheiro ProductModel.tt que será aninhado no ficheiro ProductModel.edmx
Clique duas vezes no ficheiro ProductModel.tt para o abrir no editor Visual Studio
Encontre e substitua as duas ocorrências de "ICollection" por "ObservableListSource". Estas localizam-se aproximadamente nas linhas 296 e 484.
Encontre e substitua a primeira ocorrência de "HashSet" por "ObservableListSource". Esta ocorrência situa-se aproximadamente na linha 50. Não substitua a segunda ocorrência de HashSet encontrada mais tarde no código.
Guarda o ficheiro ProductModel.tt. Isto deve fazer com que o código das entidades seja regenerado. Se o código não se regenerar automaticamente, então clique com o botão direito ProductModel.tt e escolha "Executar Ferramenta Personalizada".
Se agora abrir o ficheiro Category.cs (que está aninhado sob ProductModel.tt), deverá ver que a coleção Products tem o tipo ObservableListSource<Product>.
Compilar 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 dá-te a opção de carregar automaticamente entidades relacionadas da base de dados na primeira vez que acedes à 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 consegue carregamento preguiçoso criando instâncias de tipos proxy derivados durante o tempo de execução e depois sobrescrevendo propriedades virtuais nas suas classes para adicionar o gancho de carregamento. Para conseguir carregamento preguiçoso de objetos relacionados, tens de declarar os obtentores de propriedades de navegação como públicos e virtuais (Overridable no Visual Basic), e a tua classe não deve estar selada (NotOverridable no Visual Basic). Ao usar o Database First, as propriedades de navegação tornam-se automaticamente virtuais para permitir o carregamento preguiçoso. Na secção Código Primeiro, optámos 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 esta aplicação WinForms.
No menu principal, selecione Projeto -> Adicionar Nova Fonte de Dados ... (no Visual Studio 2010, precisa de selecionar Dados -> Adicionar Nova Fonte de Dados...)
Na janela Escolher um Tipo de Fonte de Dados, selecione Objeto e clique em Seguinte
No diálogo Selecionar os Objetos de Dados, desdobra o WinFormswithEFSample duas vezes e selecione Categoria . Não é necessário selecionar a fonte de dados do Produto, pois iremos aceder a ela através da propriedade do Produto na fonte de dados da Categoria.
Clique em Concluir. Se a janela de Fontes de Dados não aparecer, selecione Ver -> Outras Janelas -> Fontes de Dados
Carrega no ícone do pin, para que a janela de Fontes de Dados não se oculte automaticamente. Pode ser necessário carregar no botão de atualização se a janela já estivesse visível.
No Explorador de Soluções, clique duas vezes no ficheiro Form1.cs para abrir o formulário principal no designer.
Selecione a fonte de dados Categoria e arraste-a no formulário. Por defeito, são adicionados ao designer um novo controlo DataGridView (categoryDataGridView) e uma barra de ferramentas de navegação. Estes controlos também estão ligados aos componentes BindingSource (categoryBindingSource) e Binding Navigator (categoryBindingNavigator) que também são criados.
Editar as colunas no categoryDataGridView. Queremos definir a coluna CategoryId para apenas leitura. O valor da propriedade CategoryId é gerado pela base de dados depois de guardarmos os dados.
- Clique com o botão direito no controlo DataGridView e selecione Editar Colunas...
- Selecione a coluna CategoryId e defina ReadOnly como true
- Prima OK
Selecione Produtos a partir da fonte de dados da categoria e arraste para o formulário. O productDataGridView e o productBindingSource são adicionados ao formulário.
Editar as colunas no ProductDataGridView. Queremos ocultar as colunas CategoryId e Category e definir o ProductId como apenas leitura. O valor da propriedade ProductId é gerado pela base de dados depois de guardarmos os dados.
- Clique com o botão direito no controlo DataGridView e selecione Editar Colunas....
- Selecione a coluna ProductId e defina Somente leitura para True.
- Selecione a coluna CategoryId e pressione o botão Remover . Faz o mesmo com a coluna Categoria .
- Pressione OK.
Até agora, associámos os nossos controlos DataGridView aos componentes BindingSource na interface do designer. Na secção seguinte, iremos adicionar código ao código por trás para definir a categoriaBindingSource.DataSource à coleção de entidades atualmente rastreadas pelo DbContext. Quando arrastamos e soltamos Produtos sob a Categoria, o WinForms tratou de configurar a propriedade productsBindingSource.DataSource para categoryBindingSource e a propriedade productsBindingSource.DataMember para Products. Devido a esta ligação, apenas os produtos que pertencem à Categoria atualmente selecionada serão exibidos no productDataGridView.
Ative o botão Guardar na barra de Navegação clicando no botão direito do rato e selecionando Ativado.
Adicione o manipulador de eventos para o botão guardar dando um duplo clique no botão. Isto adiciona o gestor de eventos e leva-te ao código por trás do formulário. O código do gestor de eventos categoryBindingNavigatorSaveItem_Click será adicionado na secção seguinte.
Adicione o Código que gere a Interação de Dados
Agora vamos adicionar o código para usar o ProductContext para realizar o acesso aos dados. Atualize o código da janela principal do formulário, conforme mostrado abaixo.
O código define uma instância de longo prazo do ProductContext. O objeto ProductContext é usado para consultar e guardar dados na base de dados. O método Dispose() na instância ProductContext é então chamado a partir do método OnClosing sobreposto. Os comentários do 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();
}
}
}
Teste a Aplicação Windows Forms
Compila e executa a aplicação e podes testar a funcionalidade.
Depois de guardar, as chaves geradas na loja são mostradas no ecrã.
Se usou Code First, verá também que uma base de dados WinFormswithEFSample.ProductContext foi criada para si.