Entity Framework -  O mapeamento Muitos-Para-Muitos


Este série de artigos é dedicada ao Entity Framework e tem o objetivo de dar uma introdução básica e uma visão geral de como usar este importante recurso da plataforma .NET. Os artigos são parcialmente baseados na documentação da MSDN de onde são citadas as respectivas referências.

Iniciando com o Entity Framework

Conceito

A ADO .NET Entity Framework foi projetado para permitir que a criação de aplicações com acesso a dados use o modelo de programação feito contra um modelo conceitual ao invés do antigo modelo de programação feito diretamente contra um banco de dados relacional. O objetivo é diminuir a quantidade de código e o tempo de manutenção necessária exigida nestas aplicações orientada a dados.

A ADO .NET Entity Framework é uma framework que abstrai o esquema de um banco de dados relacional e o apresenta como um modelo conceitual. Abaixo a figura que representa a arquitetura em camadas do Entity Framework: (Até o lançamento do VS 2010, previsto para o primeiro trimestre.)

Data Source :  Representa a base da arquitetura onde estão armazenados os dados;

Data Providers :  Os dados são acessados por um ADO.NET data provider.  Até o momento tanto o SQL Server como o MySQL são suportados, e, provavelmente em breve os principais RDBMS do mercado (Oracle, MySQL, DB2, Firebird, Sybase, VistaDB, SQLite, ...) terão um provider para o EF.

Entity Data Model (EDM) :  O  Entity Data Model  é constituído de 3 partes:

  • Conceptual schema definition language (CSDL) : Declara e define entidades, associações, herança, etc,. As Entity classes são geradas a partir deste esquema;
  • Store schema definition language (SSDL) : Metadados que descreve o container de armazenamento (=banco de dados) que persiste os dados;
  • Mapping specification language (MSL) : Mapeia as entidades no arquivo CSDL para tabelas descritas no arquivo SSDL;

Entity Client : O EntityClient é um provedor gerenciado ADO.NET que suporta o acesso a dados descritos no EDM. Ele é similar ao SQLClient, e fornece diversos componentes como EntityCommand, EntityConnection e EntityTransaction;

Object Services : Este componente permite realizar consultas, atualizações, inclusões e exclusões nos dados, expressados como objetos CLR fortemente tipados que são instâncias de entity types. Ele dá suporte tanto a consultas Entity SQL como LINQ to Entities;

Entity SQL (ESQL) : É uma derivação da Transact-SQL, projetada para consultar e manipular entidades definidas no EDM. Ele dá suporte herança e associação sendo que tanto os componentes Object Services como os componentes Entity Client podem executar instruções Entity SQL;

LINQ to Entities : É uma linguagem de consulta fortemente tipada para consultar entidades definidas no EDM;
 

Criar, modificar e deletar objetos e aplicar estas alterações ao banco de dados é muito fácil com o Entity Framework. Através do Object Services que gerencia todas as alterações feitas nos objetos e gera e executa as instruções T-SQL que irão realizar as operações de inclusão, alteração  e exclusão contra a fonte de dados. Tudo isso é feito através da chamada do método SaveChanges do ObjectContext (equivalente ao SubmitChanges do LINQ to SQL).

Quando usamos uma ferramenta OR/M como o Entity Framework desejamos definir da melhor maneira possível o nosso modelo de entidades de forma a que tenhamos um bom desempenho e que o esforço necessário para tratar o modelo seja o mínimo possível. Como todos já sabem o modelo relacional de banco de dados não se ajusta adequadamente ao paradigma da orientação a objetos e uma ferramenta OR/M que se preze deve oferecer recursos para tornar a discrepância entre esses dois mundos o menos traumática possível. 

O relacionamento Muitos-Para-Muitos é um conceito muito comum em muitas ferramentas ORM e o Entity Framework oferece suporte ao mapeamento muitos-para-muitos,s o que será mostrado neste artigo.

O relacionamento Muitos-Para-Muitos é usado em tabelas quando ambos os lados da tabela possui um relacionamento do tipo muitos. Vejamos um exemplo clássico deste tipo de relacionamento encontrado no banco de dados Northwind.mdf.

A figura abaixo exibe as tabelas Orders, OrderDetails e Products onde temos a tabela OrderDetails exibindo em ambos os lados um relacionamento do tipo muitos.

Um relacionamento muitos-para-muitos
Num relacionamento muitos para-muitos, um registo na Tabela A pode ter muitos registos coincidentes na Tabela B, e um registro na Tabela B pode ter muitos registros coincidentes na Tabela A.

Esse tipo de relacionamento só é possível definindo-se uma terceira tabela (denominada tabela de associação) cuja chave primária consista em dois campos as chaves estrangeiras provenientes tanto da Tabelas A como da B.

Na verdade, um relacionamento muitos-para-muitos são dois relacionamentos um-para-muitos com uma terceira tabela. Por exemplo, a tabela Pedidos e a tabela Produtos têm um relacionamento muitos-para-muitos que é definido criando-se dois relacionamentos um-para-muitos para a tabela Detalhes do Pedido.
http://www.accessexemplos.com/relacionamentos/

Neste exemplo um pedido pode possuir muitos produtos e um único produto pode estar em muitos pedidos. Neste cenário geralmente usamos uma terceira tabela a qual pode incluir os relacionamentos de ambas as tabelas Pedidos(Orders) e Produtos(Products).

Aqui temos uma tabela chamada Orders que possui todos os pedidos feitos e outra tabela Products que possui todos os produtos armazenados no banco de dados. A tabela Order Details faz a conexão entre as duas tabelas e define como cada pedido esta relacionado com o produto.

A tabela Order Details possui como chave primária os campos OrderID e ProductID pois podemos ter o mesmo pedido repetido para um dado produto.

Vamos usar o Visual Studio 2008 com SP1 e criar um projeto do tipo Windows Application com o nome EF_Muitos_Muitos usando a linguagem Visual Basic e usar o Entity Framework para efetuar o mapeamento ORM do banco de dados Northwind e veremos como o EF trata o relacionamento muitos-muitos.

Abra o VS 2008 e selecione no menu File selecione New Project e em Project Types selecione Visual Basic -> WIndows , em templates selecione Windows Forms Application e informe o nome EF_Muitos_Muitos e clique em OK;

A seguir no menu Project selecione Add New Item e em templates selecione ADO .NET Entity Data Model e informe o nome Northwind.edmx (pois vamos usar o banco de dados Northwind.mdf). Clique no botão Add;

Na próxima janela selecione o item Generate From database e clique em Next>;

Na próxima janela do assistente selecione a conexão com o banco de dados Northwind e aceite o nome padrão deixando marcado item Save entity connection settings in App.Config. Clique em Next>;

Continuando selecione as tabelas Order Details , Orders e Products e clique em Finish;

O Entity Data Model gerado pelo assistente deverá estar conforme a figura abaixo

O diagrama de entidades mostra três entidade : Products, Orders e a entidade Order_Details.

Obs: Eu alterei o nome das entidades colocando-as no singular e das Entity Set no plural assim : Entity Set Name = Orders   Name = Order, etc) . Voce faz isso selecionado o modelo EDM, e no modelo selecionando a entidade e na janela de propriedades fazendo a alteração.

A entidade Order_Details está sendo exibida pois possui os atributos Quantity e Discount que são determinados pela relação entre Produtos e Pedidos se a tabela Order_Details possuísse somente os campos OrderID e ProductID ela não seria exibida no diagrama de entidades. (Esse é um benefício especial que o EF fornece quanto a tabela de junção possui somente as chaves e nenhuma coluna adicional)

Nota: Veja o artigo Entity Framework - Conceitos Básicos - Espiando o modelo conceitual para o relacionamento muitos-muitos onde a tabela de junção possui somente as chaves primárias.

Como a entidade Order_Details possui os atributos Quantity e Discount o EF a mantém no EDM gerado. Dessa forma para acessar os produtos de um pedido você vai ter que navegar através da entidade Order_Details para ter acesso a eles e vice-versa.

Vamos definir no formulário form1.vb criado por padrão uma interface bem simples para mostrar como exibir os dados relacionados em um relacionamento muitos-para-muitos.

Inclua no formulário os seguintes controles: 1 MenuStrip, 3 Labels, 3 ListBox e 1 controle Button conforme o leiaute abaixo:

O objetivo do projeto e exibir os pedidos cadastrados em um controle ListBox quando da carga do formulário e conforme um pedido for selecionado o seus detalhes serão exibidos em um segundo ListBox.

Após selecionar um pedido poderemos ver os seus produtos relacionados clicando no botão : Exibir Produtos para o pedido Selecionado.

 

Vejamos o código usado para alcançar este objetivo.

No evento Load do formulário temos o seguinte código:

 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        Using dboc As New NorthwindEntities

            Dim pedidos = From p In dboc.Orders _
                                  Select p

            lstPedidos.DisplayMember = "CustomerID"
            lstPedidos.ValueMember = "OrderID"
            lstPedidos.DataSource = pedidos.ToList

        End Using
    End Sub

O código abaixo exibe os detalhes dos pedidos quando o usuário seleciona um pedido clicando no controle ListBox:

  Private Sub lstPedidos_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstPedidos.SelectedIndexChanged

        Using dboc As New NorthwindEntities
            'Obtém o código do pedido selecionado na ListBox lstPedidos
            Dim id As Integer = lstPedidos.SelectedValue
            'Obtem o pedido selecionado
             Dim pedido = dboc.Orders.First(Function(p) p.OrderID = id)
            'exibe os detalhes do pedido
            lstDetalhesPedidos.Items.Clear()
            lstDetalhesPedidos.Items.Add("OrderID   : " & pedido.OrderID)
            lstDetalhesPedidos.Items.Add("OrderDate : " & pedido.OrderDate)
            lstDetalhesPedidos.Items.Add(pedido.RequiredDate)
            lstDetalhesPedidos.Items.Add(pedido.ShipAddress)
            lstDetalhesPedidos.Items.Add(pedido.ShipCity)
            lstDetalhesPedidos.Items.Add(pedido.ShipCountry)
        End Using
    End Sub

Após selecionar o pedido e clicar no botão para exibir os produtos o código a seguir será executado:

  Private Sub btnExibirProdutos_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExibirProdutos.Click
        'limpa os itens do ListBox lstProdutos
        lstProdutos.Items.Clear()

        'Obtém o código do pedido selecionado na ListBox lstPedidos
        Dim id As Integer = lstPedidos.SelectedValue

        Using dboc As New NorthwindEntities
            'var pedido = dboc.Orders.First( p => p.OrderID == id) C#
            Dim pedido = dboc.Orders.First(Function(p) p.OrderID = id)
            'carrega a entidade order_details relacionada
            pedido.Order_Details.Load()
            ''var produtos = pedido.Order_Details.Select(pd => pd.Products) C#
            Dim produtos = pedido.Order_Details.Select(Function(prd) prd.Products)
            'exibe o total de produtos relacionados
            lblProdutos.Text = produtos.Count
            'seleciona o pedido desejado
            Dim consulta = (From p In dboc.Orders.Include("Order_Details") _
                                   Select p _
                                   Where p.OrderID = id)
            'percorre e exibe os produtos do pedido selecionado
            For Each m In consulta
                For Each p In m.Order_Details
                         p.ProductsReference.Load()
                         lstProdutos.Items.Add(p.Products.ProductName)
                Next
            Next
        End Using
    End Sub

Vamos testar...

Acima vemos o resultado obtido após o usuário selecionar um pedido e clicar no botão para exibir os seus produtos.

Pegue o projeto completo aqui: EF_Muitos_Muitos.zip

Eu sei é apenas Entity Framework  mas mas eu gosto...

Referências:


José Carlos Macoratti