VB .NET - Atualizando tabelas relacionadas com LINQ - II
Na primeira parte deste artigo mostrei como podemos atualizar dados de tabelas relacionadas usando ADO .NET.
Usando o mesmo cenário vou mostrar, a título de comparação, como fazer a mesma tarefa usando LINQ to SQL.
Vamos ao nosso cenário...(novamente)
Vamos partir de um modelo de dados simples, e que já esta pronto para uso, e por isso vamos usar o banco de dados Northwind.mdf;
Este banco de dados possui diversas tabelas relacionadas mas nosso interesse estará focado nas tabelas: Orders, Orders Details, Suppliers, Products, Customers e Employees.
Abaixo vemos o relacionamento existente entre essas tabelas:
Nosso objetivo será atualizar as tabelas Orders e Orders Details com informações obtidas de um formulário que simulará o envio de um pedido e seus detalhes. Embora as demais tabelas não sejam atualizadas iremos precisar de suas informações.
Como temos o modelo de dados já pronto eu vou iniciar mostrando como será o formulário de entrada de dados usado na aplicação.
Os recursos necessários para acompanhar este artigo são:
Criando o mapeamento das tabelas usando LINQ to SQL
Abra o Visual Basic 2010 Express Edition e crie um novo projeto do tipo Windows Forms Application com o nome NW_Pedidos_LINQ;
Altere o nome do formulário padrão para frmLINQ.vb e inclua os seguintes controles a partir da ToolBox no formulário:
Os dois controles Combobox deverá ser preenchidos quando o formulário for carregado permitindo que o usuário selecione o cliente e o funcionário;
A seguir o usuário deverá informar o código do produto e ao teclar ENTER será realizada uma busca e o nome do produto e seu preço unitário será exibido, bastando ao usuário informar a quantidade desejada para que o valor total seja calculado e o novo pedido exibido no controle ListView;
A seguir o usuário deve informar os dados do destinatário : Nome, Endereço, Cidade e Região e clicar no botão Salvar Pedido para persistir os dados do novo pedido e seus detalhes.
Este cenário é muito frequente em aplicações comerciais.
Vamos então mostrar como implementar o código das funcionalidades envolvidas nesta operação.
Definindo o mapeamento com LINQ to SQL
Diferentemente da versão ADO .NET (primeira parte do artigo) eu não vou criar as classes do nosso domínio via código mas vou realizar o mapeamento objeto relacional usando o LINQ to SQL que é uma implementação do LINQ para o SQL Server. Dessa forma as classes serão geradas automaticamente a partir das tabelas do banco de dados.
O que é LINQ ?
- LINQ - Language integrated Query - é um conjunto de recursos introduzidos no .NET Framework 3.5 que permitem a realização de consultas diretamente em base de dados , documentos XML , estrutura de dados , coleção de objetos ,etc. usando uma sintaxe parecida com a linguagem SQL.
O que LINQ to SQL ?
LINQ to SQL é uma implementação específica to LINQ para o SQL Server que converte consultas escritas em C# ou Visual Basic em SQL dinâmico , provendo uma interface que permite mapear os objetos do banco de dados gerando as classes para realizar as operações usando a sintaxe LINQ; também permite realizar alterações nos objetos e atualizar o banco de dados.
Então através do mapeamento com o LINQ to SQL nossas classes serão geradas automaticamente e teremos acesso a elas através de um contexto gerado em nossa aplicação.
Vamos incluir em nosso projeto o recurso LINQ to SQL;
Acione o menu Project e clique em Add New Item;
Na janela Templates, selecione o item LINQ to SQL Classes alterando o seu nome para Northwind.dbml e clicando no botão Add;
Neste momento será exibida a janela do descritor Objeto Relacional. Expanda os objetos do banco de dados Northwind.mdf e selecione as tabelas Customers, Employees, Order Details, Orders, Prodcuts arrastando-as e soltando-as na janela Object Relational Designer;
As tabelas do banco de dados serão mapeadas como classes (campos como propriedades, procedures e funções como métodos) e você terá no Descritor o conjunto de classes que representam o banco de dados;
O arquivo Northwind.dbml contém o arquivo XML com informações sobre o leiaute das tabelas que foram mapeadas e também o descritor contendo as classes geradas pelo mapeamento. Após encerrar o mapeamento você já terá acesso aos recursos do LINQ To SQL com direito a intellisense completo das informações referente as tabelas mesmo sem conhecer nada sobre elas.
O acesso será feito através da criação de uma instância da classe DataContext identificada no nosso exemplo como NorthwindDataContext;
Nossa próxima tarefa é criar uma classe para tratar o contexto e criar os métodos para acessar as informações das classes que foram mapeadas pelo LINQ to SQL.
Definindo o código do projeto
Vamos incluir uma classe em nosso projeto que irá instanciar o nosso Contexto e definir nesta classe alguns métodos para acessar as informações das classes mapeadas a partir das tabelas do Northwind.mdf.
No menu Project clique em Add Class e a seguir selecione o template Class e informe o nome NWContexto.vb e clique no botão Add;
Vamos incluir uma referência ao namespace System.Transactions em nosso projeto.
No menu Project clique em Add Reference;
A seguir clique na guia .NET e selecione o componente System.Transactions e clique em OK;
O namespace System.Transactions contém classes que lhe permitem escrever a sua própria aplicação transacional e gerenciar os recursos. Você pode criar e participar em uma transação Local ou distribuída com um mais participantes.
Agora vamos definir os seguintes métodos na classe NWContexto:
O código destes métodos esta definido a seguir:
Public Class NWContexto Public Function GetProdutoPorID(ByVal productID As Integer) As Product Dim ctx As New NorthwindDataContext Dim prod = (From p In ctx.Products Where p.ProductID = productID Select p).FirstOrDefault Return prod End Function Public Function GetFuncionarios() As List(Of Employee) Dim ctx As New NorthwindDataContext Dim funcis = (From emp In ctx.Employees Select emp).ToList Return funcis End Function Public Function GetClientes() As List(Of Customer) Dim ctx As New NorthwindDataContext Dim clientes = (From cust In ctx.Customers Select cust).ToList Return clientes End Function Public Shared Function SalvarPedido(ByVal novoPedido As Order) As Integer Dim ctx As New NorthwindDataContext Using TR As New System.Transactions.TransactionScope ctx.Orders.InsertOnSubmit(novoPedido) Try ctx.SubmitChanges() TR.Complete() Catch ex As Exception Return (-1) End Try End Using Return novoPedido.OrderID End Function End Class |
Observe que em todos os
métodos criamos uma instância da classe DataContext identificada no nosso exemplo por NorthwindDataContext; Dim ctx As New NorthwindDataContext - Aqui o banco de dados é
mapeado em um DataContext permitindo acesso a
tabelas O DataContext é o
mecanismo usado para que seja feita a seleção no banco
de dados A seguir usamos consultas usando a sintaxe LINQ conforme abaixo: Dim prod = (From p In
ctx.Products A consulta LINQ To SQL
inicia com a cláusula From e em seguida o operador de
condição A cláusula From é a
mais importante do LINQ To SQL pois é usada em
todas as consultas. No exemplo o método SingeOrDefault
retorna um único, e específico elemento da sequência |
No método SalvarPedido estamos incluindo um pedido na tabela Orders usando o método InsertOnSubmit() e para
No método SalvarPedido após definirmos valores para um novo pedido estamos usando dois métodos LINQ:
- InsertOnSubmit() - Este
método adiciona uma entidade , no caso a entidade novoPedido
que é do tipo Orders, em um estado pendente de inclusão
a tabela Orders;
- SubmitChanges() - Este método efetiva a inclusão atual
feita pelo InsertOnSubmit() na tabela do banco de dados.
Deve ser chamado após o método InsertOnSubmit();
Quando SubmitChanges() é invocado o LINQ To SQL automaticamente gera e executa comandos SQL a fim de transmitir as alterações de volta ao banco de dados. Você pode no entanto sobrescrever este comportamento chamando uma stored procedure.
Vejamos agora o tratamento dos eventos do controles do formulário e as rotinas auxiliares.
No evento Load do formulário
Private Sub FrmLINQ_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim NWDados As New NWContexto cboFuncionarios.DisplayMember = "FirstName" cboFuncionarios.ValueMember = "EmployeeID" For Each func In NWDados.GetFuncionarios cboFuncionarios.Items.Add(func) Next cboClientes.ValueMember = "CustomerID" cboClientes.DisplayMember = "CompanyName" For Each cli In NWDados.GetClientes cboClientes.Items.Add(cli) Next End Sub |
No código criamos uma instância da classe NWContexto e usamos os métodos GetFuncionarios e GetClientes.
As rotinas AdicionarProduto(), AtualizaTotal() e LimpaTextBox() não sofreram alteração :
Private Sub AdicionarProduto() Dim LI As New ListViewItem LI.Text = txtID.Text LI.SubItems.Add(txtProduto.Text) LI.SubItems.Add(txtPreco.Text) LI.SubItems.Add(txtQtde.Text) LI.SubItems.Add(txtSubtotal.Text) ListView1.Items.Add(LI) AtualizaTotal() End Sub Private Sub AtualizaTotal() Dim items As Integer Dim total As Decimal For Each LI As ListViewItem In ListView1.Items items += Integer.Parse(LI.SubItems(3).Text) total += Decimal.Parse(LI.SubItems(4).Text) Next txtItems.Text = items.ToString txtTotal.Text = total.ToString("#,###.00") End Sub Private Sub LimpaTextBox() txtID.Text = "" txtProduto.Text = "" txtPreco.Text = "" txtQtde.Text = "" txtSubtotal.Text = "" End Sub |
Os eventos ColumnWidthChanged, KeyUp e SelectedIndexChanged do controle ListView também não sofreram alteração alguma:
Private Sub ListView1_ColumnWidthChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.ColumnWidthChangedEventArgs) Handles ListView1.ColumnWidthChanged txtItems.Left = ListView1.Left + ListView1.Columns(0).Width + ListView1.Columns(1).Width + ListView1.Columns(2).Width txtItems.Width = ListView1.Columns(3).Width txtTotal.Left = txtItems.Left + txtItems.Width txtTotal.Width = ListView1.Columns(4).Width End Sub Private Sub ListView1_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles ListView1.KeyUp If e.KeyCode = Keys.Delete And ListView1.SelectedItems.Count > 0 Then ListView1.SelectedItems(0).Remove() AtualizaTotal() End If If e.KeyCode = Keys.Escape Then LimpaTextBox() txtID.Focus() End If End Sub Private Sub ListView1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ListView1.SelectedIndexChanged ExibeDetalhe() End Sub |
A rotina ExibeDetalhe() também não sofreu mudança:
Private Sub ExibeDetalhe() If ListView1.SelectedItems.Count > 0 Then txtID.Text = ListView1.SelectedItems(0).Text txtProduto.Text = ListView1.SelectedItems(0).SubItems(1).Text txtPreco.Text = ListView1.SelectedItems(0).SubItems(2).Text txtQtde.Text = ListView1.SelectedItems(0).SubItems(3).Text txtSubtotal.Text = ListView1.SelectedItems(0).SubItems(4).Text End If End Sub |
No evento KeyUp do controle TxtID onde logo após o usuário digitar um código de produto ao pressionar a tecla ENTER é feita uma busca na tabela Products e são obtidos os dados do produto para exibição no formulário.
O código deste evento foi alterado conforme abaixo:
Private Sub txtID_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtID.KeyUp If e.KeyCode = Keys.Enter Then If txtID.Text.Length > 0 Then Dim P As Product = (New NWContexto).GetProdutoPorID(txtID.Text.Trim) If P IsNot Nothing Then txtProduto.Text = P.ProductName txtPreco.Text = P.UnitPrice.ToString txtQtde.Focus() Else txtID.Clear() End If End If End If If e.KeyData = Keys.Down Then If ListView1.Items.Count > 0 Then LimpaTextBox() ListView1.Items(0).Selected = True ListView1.Focus() End If End If End Sub |
Neste código estamos usando o método GetProdutoPorID() para obter o produto pelo código informado usando uma instância da classe DataContext.
Agora vamos tratar o evento KeyUp do TextBox txtQtde de forma após informar a quantidade e pressionada a tecla ENTER seja calculado o total e as informações sejam exibidas no controle ListView configurando assim um novo pedido:
Private Sub txtQtde_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtQtde.KeyUp If e.KeyData = Keys.Enter Then Dim qty As Integer Integer.TryParse(txtQtde.Text, qty) If qty > 0 Then txtSubtotal.Text = (Decimal.Parse(txtPreco.Text) * qty).ToString("#,###.00") AdicionarProduto() LimpaTextBox() txtID.Focus() Else txtQtde.Text = "" End If End If If e.KeyData = Keys.Escape Then LimpaTextBox() txtID.Focus() End If End Sub |
No evento Click do botão Salvar Pedido temos o código que salva o pedido e seus detalhes :
Private Sub btnSalvar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSalvar.Click If ListView1.Items.Count = 0 Then MsgBox("Inclua um ou mais itens para o pedido.") Exit Sub End If If cboClientes.SelectedIndex = -1 Then MsgBox("Selecione um cliente") Exit Sub End If If cboFuncionarios.SelectedIndex = -1 Then MsgBox("Selecione um funcionário") Exit Sub End If Dim novoPedido As New Order novoPedido.CustomerID = CType(cboClientes.SelectedItem, Customer).CustomerID novoPedido.EmployeeID = CType(cboFuncionarios.SelectedItem, Employee).EmployeeID novoPedido.OrderDate = Today For Each LI As ListViewItem In ListView1.Items Dim novoDetalhe As New Order_Detail novoDetalhe.ProductID = LI.Text novoDetalhe.UnitPrice = Decimal.Parse(LI.SubItems(2).Text) novoDetalhe.Quantity = Integer.Parse(LI.SubItems(3).Text) novoDetalhe.Discount = 0D novoPedido.Order_Details.Add(novoDetalhe) Next Dim ID As Integer ID = NWContexto.SalvarPedido(novoPedido) If ID > 0 Then MsgBox("Order " & novoPedido.OrderID.ToString & " submetido com sucesso...") Else MsgBox("Falha ao inserir um novo pedido no banco de dados") End If End Sub |
No evento Click do botão Novo Pedido apenas limpamos as caixas de texto e colocamos o foco no controle txtID:
Private Sub btnNovo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnNovo.Click LimpaTextBox() txtID.Focus() End Sub |
Agora é só alegria...
Mas antes de prosseguir vamos comparar o que mudou com a utilização do LINQ to SQL em nosso projeto.
Notou que não tivemos que utilizar instruções SQL para acessar e persistir informações no banco de dados ?
Notou também que não tivemos que nos preocupar com a definição de objetos connection, command, adapter, etc...
A utilização do LINQ to SQL abstraiu toda essa parte permitindo assim um ganho de produtividade pois o projeto ficou mais enxuto.
Vamos executar o projeto e incluir um novo pedido...
Após informar os dados e clicar no botão - Salvar Pedido - teremos o seguinte resultado:
Dessa forma neste projeto tivemos que definir as nossas classes de negócio para poder implementar a funcionalidade desejada e ao invés de fazer isso via código usamos o LINQ to SQL para gerar as classes via mapeamento OR/M.
Pegue o projeto completo aqui: NW_Pedidos_LINQ.zip
Veja os Destaques e novidades do SUPER CD VB 2012 (sempre atualizado) : clique e confira ! Quer migrar para o VB .NET ? Veja mais sistemas completos para a plataforma .NET no Super CD .NET e no Super DVD .NET , confira... Quer aprender C# ?? Chegou o
Super DVD C# 2012
com exclusivo material de suporte e vídeo aulas
com curso básico sobre C# |
1Pe 2:1
Deixando, pois, toda a malícia, todo o engano, e fingimentos, e invejas, e toda a maledicência,Referências: