VB .NET 2008 - LINQ To SQL - Criando Mestre-Detalhe
Vamos dar mais um passo no nosso aprendizado do LINQ apresentando neste artigo como criar relacionamentos um-para-muitos ou modelo mestre-detalhe usando a sintaxe LINQ.
Se você esta tendo o seu primeiro contato com o LINQ veja os artigos publicados:
Requisitos necessários para acompanhar este artigo:
Abra o VB 2008 Express e crie um novo projeto no menu FIle->New Project do tipo Windows Forms Application com o nome LINQMestreDetalhes (ou algo mais sugestivo);
A seguir vamos definir uma conexão com o banco de dados Northwind.mdf, se a conexão ainda não existir no seu ambiente faça o seguinte:
1- No menu View e selecione DataBase Explorer e clique com o botão direito do mouse sobre o item Data Connections e selecione Add Connection...
2- Na janela Add Connection Clique no botão Browse e selecione o banco de dados Northwind.mdf a partir do local onde você o instalou e clique no botão Abrir;
Agora vamos incluir a referência ao LINQ To SQL através do template LINQ To SQL Classes;
No menu Project opção Add New Item selecione na janela Templates o template LINQ to SQL Classes alterando o nome para Northwind.dbml pois iremos efetua a conexão com o banco de dados Northwind.mdf (você pode usar qualquer nome);
Neste momento será exibida a janela do Descritor Objeto Relacional e você deverá ter a seguinte visão do seu projeto no VB 2008;
Expanda os objetos do banco de dados Northwind.mdf e selecione a tabela Orders e a tabela Customers arrastando e soltando na janela Object Relational Designer;
Quando você arrasta e solta as tabelas Orders e Customers a partir da janela DataBase Explorer no descritor LINQ To SQL, o VB 2008 irá inspecionar os relacionamentos chave primária/chave estrangeira dos objetos e baseado neles irá criar associações de relacionamentos padrão entre as diferentes entidades de classes criadas.
No exemplo existe uma relação um-para-muitos entre a tabela Customers e Orders onde a vinculação é feita pela chave CustomerID onde um cliente pode possuir muitos pedidos. Neste caso Customers é a tabela Pai e Orders a tabela Filha.
No Descritor OR vemos que foram criadas duas classes - Customer e Product - onde as propriedades definidas estão mapeadas para as colunas de suas respectivas tabelas.
Vamos voltar ao formulário form1.vb e a partir da janela Data selecionar a opção Add New Data Source;
Na janela do assistente selecione a opção Object pois iremos nos conectar com o modelo criado pelo LINQ, Clique em Next>
Na próxima janela expanda o modelo de objetos criado e selecione o objeto Customer. Clique em Next> e depois em Finish.
Após isto você deverá ver na janela Data Sources as fontes de dados Customers e Orders;
Selecione Customers e arraste e solte a fonte de dados no formulário form1.vb;
Você verá a criação de um DataGridView e dos objetos CustomerBindingSource e CustomerBindingNavigator;
Em seguida ajuste o leiaute do formulário e a partir da janela Data Sources selecione, arraste e solte a fonte Orders para o formulário:
Será criado o DataGridView dos pedidos (Orders) e do objeto OrdersBindingSource;
Com isso estamos com o ambiente preparado para que havendo uma seleção de um Customer os seus pedidos (Orders) serão exibidos.
Agora temos criar o código para preencher os objetos. A primeira coisa a fazer é criar uma referência a objeto DataContext:
Private db As New NorthwindDataContext
O DataContext é o gerenciador do mapeamento objeto relacional efetuado pelo LINQ entre as classes criadas e as tabelas do banco de dados que permite a realização de consultas e atualizações no banco de dados via sintaxe LINQ.
Agora no evento Load do formulário form1.vb inclua o seguinte código:
Private Sub Form1_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load Me.CustomerBindingSource.DataSource = db.Customers End Sub |
Rode o projeto e veja o resultado:
Isso mesmo !!! Os clientes são carregados no primeiro Grid e os pedidos no segundo e , se você selecionar um determinado cliente irá perceber que o seus pedidos são exibidos no segundo grid. Tudo isso com duas linhas de código. (E, uma pequena ajuda do LINQ...)
Para vermos como as coisas funcionam vamos incluir a linha de código : db.Log = Console.Out no evento Load e rodar novamente o projeto observando a saída na janela Output;
Observe que são exibidas duas instruções SQL , a primeira referente a carga dos clientes no primeiro Grid e a segunda referente aos pedidos de um determinado cliente. Neste caso o parâmetro usado é o código do cliente (CustomerID) que é chave de relacionamento entre as tabelas.(Veja o parâmetro ALFKI no exemplo). Desta forma cada seleção no primeiro grid irá gerar um novo Select com o customerID selecionado. Isso é conhecido pelo nome de lazy loading.
Nota: Lazy loading, também conhecida como dynamic function loading, é um modo que permite o desenvolvedor especificar quais componentes de uma programação não serão carregados no armazenamento por padrão quanto o programa começar a rodar. O LINQ To SQL pode fazer determinado atributo ser carregado utilizando Lazy Loading / Delay, basta você selecionar a propriedade da classe e definir a propriedade "Delay Loaded" para True. Isso quer dizer que só são trazidos os dados que são utilizados, ou seja, são obtidos por demanda. Isso implica em melhoria de performance e menos consumo de memória.
Vamos agora tratar de permitir que os dados sejam persistidos no banco de dados . No formulário form1.vb selecione o ícone Save e clique com o botão direito do mouse sobre ele escolhendo a opção Enabled no menu suspenso.
A seguir clique duas vezes sobre o ícone habilitado e no evento Click o código que irá salvar os objetos no banco de dados:
Private Sub
CustomerBindingNavigatorSaveItem_Click(ByVal sender As
System.Object, ByVal e As System.EventArgs) Handles
CustomerBindingNavigatorSaveItem.Click Me.Validate() Me.CustomerBindingSource.EndEdit() Me.OrdersBindingSource.EndEdit() Try db.SubmitChanges() MsgBox("Salvo") Catch ex As Exception MsgBox("Erro ao salvar : " & ex.Message) End Try End Sub |
O método SubmitChanges do objeto DataContext define o conjunto de objetos modificados que serão incluídos, atualizados ou excluídos e executa o comando apropriado para implementar as alterações no banco de dados.
A seguir rode o projeto e clique no botão para incluir um novo cliente/pedido e altere também os dados de um cliente clicando a seguir no botão Save;
Perceba que inclui um novo cliente e seu pedido e alterei o campo ContactTitle da tabela Customers. Clicando no botão Save as alterações são salvas no banco de dados.
Agora veja abaixo a instrução SQL gerada pelo LINQ:
SELECT
[t0].[CustomerID], [t0].[CompanyName],
[t0].[ContactName], [t0].[ContactTitle], [t0].[Address],
[t0].[City], [t0].[Region], [t0].[PostalCode],
[t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry] FROM [dbo].[Orders] AS [t0] WHERE [t0].[CustomerID] = @p0 -- @p0: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [ALFKI] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName], [t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion], [t0].[ShipPostalCode], [t0].[ShipCountry] FROM [dbo].[Orders] AS [t0] WHERE [t0].[CustomerID] = @p0 -- @p0: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [WOLZA] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 INSERT INTO [dbo].[Customers]([CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10) -- @p0: Input NChar (Size = 5; Prec = 0; Scale = 0) [MACOR] -- @p1: Input NVarChar (Size = 7; Prec = 0; Scale = 0) [JcmSoft] -- @p2: Input NVarChar (Size = 9; Prec = 0; Scale = 0) [Macoratti] -- @p3: Input NVarChar (Size = 4; Prec = 0; Scale = 0) [Ower] -- @p4: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p5: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p6: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p7: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p8: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p9: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p10: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 INSERT INTO [dbo].[Orders]([CustomerID], [EmployeeID], [OrderDate], [RequiredDate], [ShippedDate], [ShipVia], [Freight], [ShipName], [ShipAddress], [ShipCity], [ShipRegion], [ShipPostalCode], [ShipCountry]) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12) SELECT CONVERT(Int,SCOPE_IDENTITY()) AS [value] -- @p0: Input NChar (Size = 5; Prec = 0; Scale = 0) [MACOR] -- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [1] -- @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [11/10/2006 00:00:00] -- @p3: Input DateTime (Size = 0; Prec = 0; Scale = 0) [Null] -- @p4: Input DateTime (Size = 0; Prec = 0; Scale = 0) [Null] -- @p5: Input Int (Size = 0; Prec = 0; Scale = 0) [Null] -- @p6: Input Money (Size = 0; Prec = 19; Scale = 4) [Null] -- @p7: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p8: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p9: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p10: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p11: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- @p12: Input NVarChar (Size = 0; Prec = 0; Scale = 0) [Null] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 UPDATE [dbo].[Customers] SET [ContactTitle] = @p10 WHERE ([CustomerID] = @p0) AND ([CompanyName] = @p1) AND ([ContactName] = @p2) AND ([ContactTitle] = @p3) AND ([Address] = @p4) AND ([City] = @p5) AND ([Region] IS NULL) AND ([PostalCode] = @p6) AND ([Country] = @p7) AND ([Phone] = @p8) AND ([Fax] = @p9) -- @p0: Input NChar (Size = 5; Prec = 0; Scale = 0) [WOLZA] -- @p1: Input NVarChar (Size = 14; Prec = 0; Scale = 0) [Wolski Zajazd] -- @p2: Input NVarChar (Size = 23; Prec = 0; Scale = 0) [Zbyszek Piestrzeniewicz] -- @p3: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [Owner] -- @p4: Input NVarChar (Size = 15; Prec = 0; Scale = 0) [ul. Filtrowa 68] -- @p5: Input NVarChar (Size = 8; Prec = 0; Scale = 0) [Warszawa] -- @p6: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [01-012] -- @p7: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [Poland] -- @p8: Input NVarChar (Size = 13; Prec = 0; Scale = 0) [(26) 642-7012] -- @p9: Input NVarChar (Size = 13; Prec = 0; Scale = 0) [(26) 642-7012] -- @p10: Input NVarChar (Size = 8; Prec = 0; Scale = 0) [OwerXXXX] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8 |
Note o Select inicial e a seguir o INSERT INTO para incluir o novo registro e o comando UPDATE para atualizar o campo alterado.
Vamos agora incrementar um pouco a nossa aplicação permitindo filtrar os clientes para um determinado país selecionado.
No formulário form1.vb selecione o CustomerBindingNavigator e inclua um combobox, clicando sobre o controle e selecionando ComboBox:
Vamos preencher o combobox com os paises. Para isso no evento Load , comente o código existente e inclua o seguinte código:
Private Sub
Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load db.Log = Console.Out 'Me.CustomerBindingSource.DataSource = db.Customers Dim paises = From clientes In db.Customers _ Where clientes.Country <> "" _ Order By clientes.Country _ Select clientes.Country Distinct For Each pais In paises Me.ToolStripComboBox1.Items.Add(pais) Next End Sub |
Feito isso temos que refletir a seleção de um pais no DataGridView para que sejam exibidos os clientes para o país selecionado no ComboBox.
Então no evento SelectedIndexChanged do ToolStripCombobox vamos incluir o código que filtra os clientes por pais usando LINQ To SQL;
Private Sub
ToolStripComboBox1_SelectedIndexChanged(ByVal sender As
Object, ByVal e As System.EventArgs) Handles
ToolStripComboBox1.SelectedIndexChanged Dim clientes = From cliente In db.Customers _ Where cliente.Country = Me.ToolStripComboBox1.SelectedItem.ToString _ Select cliente Me.CustomerBindingSource.DataSource = clientes End Sub |
Executando o projeto o formulário será apresentando vazio , onde teremos que selecionar um país a partir da combobox. Feito isso os clientes e os pedidos dos clientes para o pais selecionado é exibido conforme figuras abaixo:
É sempre uma boa idéia limitar a quantidade de dados exibida na carga de um formulário, com isso temos desempenho e evitamos problemas.
Espero que ao final deste artigo os seus conhecimentos sobre LINQ To SQL estejam mais sólidos
Pegue o projeto completo aqui: LINQToSQL_MestreDetalhes.zip
Eu sei é apenas .NET mas eu gosto...
Referências:
LINQ -
http://msdn2.microsoft.com/en-us/netframework/aa904594.aspx
LINQ To SQL -
http://msdn2.microsoft.com/en-us/library/bb425822.aspx
ScottGu´s Blog -
http://weblogs.asp.net/scottgu/archive/2006/09/01/Understanding-LINQ-to-SQL-Query-Translations.aspx
Exemplos-
101 LINQ Samples
José Carlos Macoratti