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
de forma transparente sem nos preocuparmos com conexão. O DataContext utiliza
a interface IDbConnection do ADO.NET para acessar o armazenamento e pode ser
inicializado tanto com um objeto de conexão ADO.NET estabelecido ou com uma string
de conexão que possa ser utilizada para criar a conexão.

O DataContext é o mecanismo usado para que seja feita a seleção no banco de dados
ficando responsável por traduzir as seleções e alterações executando-as no banco de
dados e transformando o resultado em objetos.

A seguir usamos consultas usando a sintaxe LINQ conforme abaixo:

Dim prod = (From p In ctx.Products
                  Where p.ProductID = productID
                  Select p).FirstOrDefault

A consulta LINQ To SQL inicia com a cláusula From e em seguida o operador de condição
Where depois ordenação com Order By e, no final o operador de seleção Select.

A cláusula From é a mais importante do LINQ To SQL pois é usada em todas as consultas.
Uma consulta deve sempre começar com From. (
O Select pode estar implícito o From não.)

No exemplo o método SingeOrDefault retorna um único, e específico elemento da sequência
de valores ou um valor padrão se o elemento não foi encontrado.

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,
1Pe 2:2
desejai como meninos recém-nascidos, o puro leite espiritual, a fim de por ele crescerdes para a salvação,
1Pe 2:3
se é que já provastes que o Senhor é bom;
1Pe 2:4
e, chegando-vos para ele, pedra viva, rejeitada, na verdade, pelos homens, mas, para com Deus eleita e preciosa,
1Pe 2:5
vós também, quais pedras vivas, sois edificados como casa espiritual para serdes sacerdócio santo, a fim de oferecerdes sacrifícios espirituais, aceitáveis a Deus por Jesus Cristo.

Referências:


José Carlos Macoratti