Usando Datasets Tipados Por Mauro Sant’Anna ([email protected]). Mauro é um “MSDN Regional Director”, consultor e instrutor da MAS Informática (www.mas.com.br), tendo ministrado treinamentos na arquitetura .NET desde outubro de 2000. Uma das características da arquitetura de bancos de dados ADO.NET é a facilidade para trabalhar em modo desconectado. Este modo é o que se adapta melhor ao uso em conjunto com servidores de aplicativos, onde a manutenção de estado através de cursores ou conexões abertas é um sério empecilho a performance e “escalabilidade”. O “Typed DataSet” é o tipo perfeito para ser usado como “moeda de troca” entre as diversas “camadas” do seu aplicativo. Saber como criá-los e manipulá-los é essencial para utilizá-los bem. Não pretendo neste artigo entrar em méritos de metodologia e projetos em múltiplas camadas, mas os leitores que tiverem experiência com estas disciplinas com certeza conseguirão reconhecer nos Typed Datasets grandes aliados. Este artigo supõe uma certa familiaridade com o Visual Studio .NET e também que você tenha um servidor de banco de dados SQL Server instalado para testes. Criando um projeto de testes Crie um novo projeto do tipo “Windows Application” em Visual Basic. Adicione ao formulário um componente OleDbDataAdapter. A seguinte caixa de diálogo aparecerá: Pressione “Next”: Vamos agora criar uma conexão à base “Northwind”, um banco de dados de exemplo que acompanha o SQL Server. Clique em “New Connection” e entre os dados abaixo (evidentemente o nome do servidor será diferente, correspondendo a algum servidor na sua rede): Clique “Ok”; você voltará à tela anterior, mas com a base “Northwind” selecionada: Clique “Next”: Clique “Next” novamente e entre a query “select * from Products”: Clique em “Finish”. Observe que foram criados vários componentes: OleDbConnection1: Visível no formulário; encapsula a conexão ao banco de dados; OleDbDataAdapter1: Visível no formulário; fará a comunicação entre o DataSet e a base de dados; OleDbSelectCommand1: Não visível no formulário; encapsula um comando SQL SELECT. OleDbInsertCommand1, OleDbUpdateCommand1, OleDbDeleteCommand1: Não visíveis no formulário; encapsulam comandos SQL de atualização logicamente associados ao comando SQLECT. É possível modificar os comandos SQL criados, caso eles não sejam adequados. Por exemplo, você pode desejar omitir a atualização em um campo calculado ou de “autoincremento”. Criando Datasets tipados Os DataSets tipados podem ser criados de diversas maneiras: 1. Adicionando um DataSet ao projeto em “Project | Add New Item | Dataset”; 2. A partir de um componente DataAdapter, tendo como base o conjunto de resultado de uma consulta a banco de dados. No nosso caso, usaremos a segunda maneira. Clique com o botão direito no componente OleDbDataAdapter1 ou no formulário e selecione “Generate DataSet”: Criaremos um novo DataSet com o nome “Northwind_Products”: Entre o nome e clique “Ok”. O projeto terá agora uma classe DataSet no “Solution Explorer” e uma instância desta classe no formulário: Observe que NorthWind_Products representa uma classe. Você pode pedir “Show All Files” no “Solution Explorer” e abrir o arquivo “Northwind_Products.vb” que contém o código fonte desta classe. Este fonte foi criado automaticamente e não faz sentido modificá-lo. Esta classe pode ser usada em diversas situações: Como tipo de parâmetro de uma função que implementa alguma regra de negócio ou atualiza a base de dados; Como tipo de retorno de uma função que efetua uma consulta ao banco de dados; Como base para criação de instâncias não só do próprio DataSet, como também das tabelas e linhas das tabelas que ele contém para manipulações em lote. Já “Northwind_Products1” representa uma instância de NorthWind_Products. Esta instância pode ser usada em diversas situações, por exemplo: Como argumento do método Fill de um componente DataAdapter para efetuar uma consulta e preenchê-lo com o conjunto de resultado; Como argumento do método Update de um componente DataAdapter para atualizar a base de dados usando os comandos de atualização associados ao DataAdapter; Para atualizações com código “batch” de programação; Como argumento de funções. Preenchendo e atualizando Veremos como usar o DataSet em um aplicativo. Acrescente componentes de forma a ter o formulário parecido com o seguinte: Note que o componente na parte inferior é um DataGrid. Ajustamos duas de suas propriedades, de forma a se referir à tabela Products do nosso DataSet: DataSource: Northwind_Products1; DataMember: Products. O preenchimento do DataSet é feito a partir do botão “Consulta” com o método Fill do DataAdapter: Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click ' Preenche o DataSet com o resultado da consulta OleDbDataAdapter1.Fill(Northwind_Products1) End Sub Note um detalhe importante: embora estejamos preenchendo o DataSet com o conteúdo completo de uma tabela, este procedimento só deve ser feito com tabelas pequenas. O mais comum é usar uma query parametrizada com uma cláusula WHERE, de forma a limitar o tamanho do conjunto de resultado. Depois de preencher o DataSet, o usuário poderá atualizá-lo através de código ou diretamente no DataGrid. Para enviar as atualizações de volta ao banco de dados (como no botão “Atualiza”), o código é o seguinte: ' Submete atualizações Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click ' Verifica se existem mudanças If Northwind_Products1.HasChanges() Then ' Pega as modificações Dim Modificados As DataSet = Northwind_Products1.GetChanges() ' Existem mesmo? If Not Modificados Is Nothing Then ' Submete modificações ao banco OleDbDataAdapter1.Update(Modificados) ' Limpa buffer de dados modificados Northwind_Products1.AcceptChanges() End If End If End Sub Observe que extraímos as mudanças para outro DataSet que por sua vez é passado como argumento do método Update do DataAdapter. O método Update envia ao banco de dados as atualizações efetuadas em uma instância em memória do DataSet. Atualizações Batch É possível atualizar a instância do DataSet em memória tanto através de controles vinculados, como no caso do DataGrid, como também através de código. Veja alguns exemplos. Atualiza um campo de todas as linhas As tabelas dentro do DataSet não possuem “cursores” ou “registro corrente”. Elas são acessadas como se fossem arrays. Veja duas versões para código que aumenta preço de todos os produtos. ' Aumenta preço, versão 1 Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click ' Declara uma instância da linha Dim Linha As Northwind_Products.ProductsRow ' Varre a tabela inteira For Each Linha In Northwind_Products1.Products ' Modifica Linha.UnitPrice *= 1 + (NumericUpDown1.Value / 100) Next End Sub ' Aumenta preço, versão 2 Private Sub Button8_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button8.Click ' Declara uma instância da linha Dim Linha As Northwind_Products.ProductsRow ' Declara variável de controle de loop Dim I As Integer ' Varre a tabela inteira For I = 0 To Northwind_Products1.Products.Rows.Count - 1 ' Pega a linha Linha = Northwind_Products1.Products(I) ' Modifica Linha.UnitPrice *= 1 + (NumericUpDown1.Value / 100) Next End Sub Insere um registro Podemos inserir registros passando os valores de cada campo. Estes valores podem vir de controles não-vinculados, programas de importação ou qualquer outra fonte. Veja um exemplo que insere um registro com valores fixos. Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click ' Insere uma linha com valores específicos Northwind_Products1.Products.AddProductsRow( _ "Cerveja Touro Azul", 10, 1, "12 12oz cans", 6.45D, 100, 250, 150, False) End Sub Pesquisa dada chave primária Cada tabela de um DataSet tipado contém um método que pesquisa a existência de uma linha dada uma chave primária. Veja um exemplo que pesquisa dado o código do produto. Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click ' Converte ID para inteiro Dim ID As Integer = Convert.ToInt32(TextBox1.Text) ' Declara uma instância da linha Dim Linha As Northwind_Products.ProductsRow = Northwind_Products1.Products.FindByProductID(ID) ' Achou alguma coisa? If Linha Is Nothing Then MsgBox("Não Achado") Else MsgBox("Achado: " & Linha.ProductName) End If End Sub Filtros Podemos criar um filtro com o componente DataView, mas uma alternativa é usar o método Select do DataTable. Este método aceita uma expressão semelhante a uma cláusula WHERE do SQL e retorna um array de linhas que atendem ao critério. O exemplo abaixo efetua pesquisa dado o nome do produto. Private Sub Button7_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button7.Click ' Declara um array de linhas Dim Linhas() As DataRow ' Cria um filtro Linhas = Northwind_Products1.Products.Select("ProductName = '" & TextBox1.Text.Trim() & "'") ' Verifica se a pesquisa achou alguma coisa If Linhas.Length > 0 Then ' Declara uma linha Dim Linha As Northwind_Products.ProductsRow ' Pega a primeira linha Linha = CType(Linhas(0), Northwind_Products.ProductsRow) MsgBox(Linha.ProductID & " - " & Linha.ProductName) Else MsgBox("Não Achado") End If End Sub Apagando Para apagar um registro basta chamar o método Remove de DataTable. Usualmente efetuamos uma pesquisa antes para saber que linha estamos apagando. O exemplo abaixo apaga um registro dado o código do produto. Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click ' Converte ID para inteiro Dim ID As Integer = Convert.ToInt32(TextBox1.Text) ' Declara uma instância da linha Dim Linha As Northwind_Products.ProductsRow = Northwind_Products1.Products.FindByProductID(ID) ' Achou alguma coisa? If Linha Is Nothing Then MsgBox("Não Achado") Else Northwind_Products1.Products.Rows.Remove(Linha) End If End Sub Conclusão Os DataSets tipados facilitam bastante a manipulação de bancos de dados de forma desconectada.