Clique aqui para ler este artigo em pdf imagem_pdf.jpg

msdn02_capa.jpg

Clique aqui para ler todos os artigos desta edição

 

Trocando dados entre Objetos em um aplicativo

Qual é a melhor forma de passar dados entre camadas em um aplicativo?

por Ken Spencer

 

A tarefa de passar dados entre formulários, classes, páginas da Web e Web Services é sempre um desafio. Embora à primeira vista essa tarefa possa parecer bastante simples, as necessidades de um aplicativo podem exigir requisitos diferentes para os dados no destino (endpoints) e requerer certas alterações em seu formato.

  De quantas maneiras é possível mover dados entre componentes de uma operação? Vamos tentar fazer uma pequena lista:

        Estruturas;

        Classes (com e sem serialização);

        Vetores (arrays), coleções (collections);

        DataSets, DataTables, DataRows;

        XML;

        Variáveis compartilhadas (Shared variables);

        Propriedades públicas expostas a partir de classes.

 

Existem outros métodos, mas esses são alguns dos mais comuns. Vamos usar algumas dessas abordagens e examinar os prós e os contras de cada uma delas. Antes de começar, você deve dedicar um tempo para pensar no que irá fazer com os dados quando eles chegarem a seu destino. Se você pretende usá-los junto com um DataSet, DataTable ou DataRow, o melhor é usar um formato que seja facilmente transferível (ou seja, um formato que seja traduzido diretamente para o formato desejado, como um DataRow).

Primeiro, vamos dar uma olhada nas estruturas e classes, pois elas podem ter propriedades, métodos e construtores. Entretanto, existem algumas diferenças nesses conceitos e você poderá ler mais sobre elas no livro Migrando para o VB.NET: Estratégias, Conceitos e Código, de Dan Appleman. Agora, vamos analisar alguns aspectos práticos que descobri sobre estruturas e classes.

As estruturas são muito interessantes porque também se comportam como classes "leves", pois ficam sobre a pilha (stack) ao invés da heap, desde que você use tipos de valor. Uma estrutura simples se assemelha ao código abaixo:

 

Public Structure SomeData

    Dim Name As String

    Dim Description As String

End Structure

 

A estrutura é usada assim:

 

Public MyStuff As SomeData

 

Também é possível usar e retornar uma estrutura a partir de uma função, como mostrado abaixo:

 

Public Function DoSomething(ByVal Name As String) As SomeData

    With MyStuff

        .Name = Name

        .Description = "Good stuff"

    End With

    Return MyStuff

End Function

 

Como você pode ver, a função carrega a estrutura com os dados e, em seguida, a retorna a partir da função. Agora, vamos chamar a função a partir de outro aplicativo, ou, para ser mais preciso, a partir de dois aplicativos diferentes:

 

Dim oMyClass As New SomeComponent.Class1()

Dim MyStuff As SomeComponent.Class1.SomeData

MyStuff = oMyClass.DoSomething(GetUserName)

txtDescription.Text = MyStuff.Description

txtName.Text = MyStuff.Name

oMyClass = Nothing

 

Esse código retorna o nome do usuário atual e a descrição dentro da classe. Agora, vamos chamar esse método a partir de um aplicativo da Web. Esse exemplo também funcionará bem até que você insira o componente no COM+. Se isso acontecer, você receberá uma mensagem de erro informando que a classe não está marcada para serialização. Esse problema pode ser resolvido adicionando-se um atributo de serialização à classe:

 

_

    Public Class Class1

 

Isso resolve esse problema. Até aqui, tudo bem. Estou desenvolvendo um aplicativo multicamada para um cliente e o aplicativo usa estruturas, vetores (arrays), classes, datasets e outros itens em diferentes pontos. Em um dos casos, combino estruturas e bancos de dados por meio da criação de uma estrutura que contenha um dataset e algumas outras informações.

A estrutura a seguir pode armazenar um DataTable e uma string. É possível passa-la com dados do SQL Server™ e também com outros tipos de dados:

 

Public Structure CustomerStuff

    Dim MoreInfo As String

    Dim dt As DataTable

End Structure

 

A função que retorna essa estrutura é mostrada na Listagem 1. RunSQLWithDataSet está disponível no código para download deste artigo no site da revista.

Listagem 1 – Função ReturnSomedata

Function ReturnSomedata() As CustomerStuff

    Dim custstuff As CustomerStuff

    Dim ds As DataSet

    Const ConnectionString = _

      getTrustedConnection()

    ds = RunSQLWithDataSet( _

      "Select * from customers", ConnectionString)

    custstuff.dt = ds.Tables(0)

    custstuff.MoreInfo = "This was really cool"

    Return custstuff

End Function

 

O novo método é usado da mesma forma que o outro:

 

Dim oMyClass As New SomeComponent.Class1()

Dim MyCustomer As SomeComponent.Class1.CustomerStuff

MyCustomer = oMyClass.ReturnSomedata

txtDescription.Text = MyCustomer.MoreInfo

DataGrid1.DataSource = MyCustomer.dt

oMyClass = Nothing

 

Como você pode ver, a estrutura fornece uma boa maneira de tratar vários tipos de dados em um único local. Isso resolve o problema de retornar dados de uma função que possua mais de um tipo de retorno. Essa abordagem é mais simples do que tentar criar uma classe que preencha o DataSet junto com os outros dados. Ela também é mais simples do que tentar estender o próprio DataSet para alimentar os dados adicionais.

 

Agora, vamos às desvantagens associadas com as estruturas. Eu me deparei com dois problemas: primeiro, as estruturas não oferecem suporte a eventos. Se você tentar usar eventos em uma estrutura, nada acontecerá quando o aplicativo for executado. Desse modo, as classes é que ditam a regra. Em segundo lugar, tive problemas com as estruturas e o Visual Studio® .NET. Criei uma biblioteca de dados (data library) que contém uma função semelhante à analisada anteriormente e que retorna uma estrutura. Como ela funcionava adequadamente quando eu a chamava a partir do cliente, concluí que seria seguro colocá-la em um componente separado ao qual o cliente e a biblioteca de dados pudessem fazer referência. Isso permitiria à biblioteca de dados e à interface com o usuário fazerem referência ao terceiro componente. Quando tentei usar essa abordagem, o Visual Studio .NET insistiu em solicitar que eu adicionasse uma referência ao projeto para o assembly que continha a estrutura. A questão é que eu já havia incluído essa referência! No fim das contas, incluí a estrutura na biblioteca de dados, permitindo que a camada intermediária e a interface com o usuário fizessem referência a ela. Funciona, mas não de forma tão perfeita quanto eu imaginara.

 

Agora, vamos examinar o uso de classes. Cabe ressaltar que você pode vincular aos controles tanto as classes quanto as estruturas. Assim, você configura um controle uma única vez e vincula a uma fonte de dados (datasource) para sempre. Como ambos são sincronizados, você não precisa se preocupar com a atualização de um ou de outro.

 

Vamos considerar um exemplo simples, no qual você precise trabalhar com dados do cliente. Você poderia inserir esses dados tanto em uma estrutura quanto em uma classe. Qual seria a diferença? Bem, isso depende do que você quer fazer com os dados. Se quiser vincular os dados a controles e facilitar o seu trabalho, as classes são uma boa opção porque podem disparar eventos, como é mostrado na Listagem 2.

 

Listagem 2 – Usando classes

Public Class Customer

    Private privateCustomerID As String

    Private privateCompanyName As String

    Private privateContactName As String

 

    Public Event CustomerIDChanged As EventHandler

    Public Event CompanyNameChanged As EventHandler

    Public Event ContactNameChanged As EventHandler

 

    Public Property CustomerID() As String

        Get

            Return privateCustomerID

        End Get

        Set(ByVal Value As String)

            privateCustomerID = Value

            RaiseEvent CustomerIDChanged(Me, New EventArgs())

 

        End Set

    End Property

 

    Public Property CompanyName() As String

        Get

            Return privateCompanyName

        End Get

        Set(ByVal Value As String)

            privateCompanyName = Value

            RaiseEvent CompanyNameChanged(Me, New EventArgs())

        End Set

    End Property

 

    Public Property ContactName() As String

        Get

            Return privateContactName

        End Get

        Set(ByVal Value As String)

            privateContactName = Value

 

            RaiseEvent ContactNameChanged(Me, New EventArgs())

        End Set

    End Property

 

    Sub Reset()

        CustomerID = ""

        CompanyName = ""

        ContactName = ""

    End Sub

End Class

 

Existem dois recursos importantes nessa classe simples. Primeiro, ela dispara eventos importantes (como o CustomerIDChanged) sempre que uma propriedade é modificada. A arquitetura de vinculação de dados presta atenção a esses eventos e, quando algum deles é disparado, ela sincroniza novamente os dados vinculados e recarrega os controles. O seu código só precisará chamar o evento quando você alterar uma propriedade.

Essa classe mostra outro importante recurso. No aplicativo que estava desenvolvendo, eu precisava redefinir a classe e fazer com que essa ação zerasse ou redefinisse os controles vinculados. Tentei recriar a instância da classe desta forma:

 

Dim CurrentCustomer As New Customer()

 

Não funcionou. Finalmente, encontrei uma solução elegante: um método "Reset" que pode ser chamado sempre que um aplicativo precisar atualizar a classe. Quando o método Reset é chamado, ele automaticamente faz com que todos os controles vinculados sejam atualizados para o estado correto, limpando o formulário para uma nova entrada. 

Agora, vamos nos concentrar nos DataSets por um minuto. Vou chamar o método Update do SQLDataAdapter e passá-lo para um DataSet tipado. Nenhum problema, certo? Bem, não é exatamente assim. Vamos examinar um exemplo. Esta função retornará um DataSet com dados do cliente após chamar a função RunSQLWithDataSet, como mostra o código a seguir:

 

Function RetrieveCustomerContacts() As DataSet

    Dim ds As DataSet

    ds = RunSQLWithDataSet("Select CustomerID, " & _

    " CompanyName, ContactName from customers", _

    ConnectionString, "Customers")

    Return ds

End Function

 

O último parâmetro para RunSQLWithDataSet especifica o nome da tabela retornada no DataSet. Essa informação é extremamente importante, como você verá a seguir. O aplicativo cliente usa um DataSet tipado, que é carregado por meio do evento Form_Load do Windows® Form conforme o código abaixo:

 

Dim oData As New SomeComponent.Class1()

Dim ds As DataSet

ds = oData.RetrieveCustomerContacts

CustomerContactInfo1.Merge(ds.Tables(0))

 

A parte complicada desse código consiste na última linha, que chama o método Merge do DataSet tipado. Ele usa o DataSet retornado pela função e o mescla à tabela Customers do DataSet tipado. O segredo está no fato de que o schema e o nome da tabela não tipada (untyped table) devem ser correspondentes, ou o método Merge não funcionará como o esperado; a tabela será adicionada ao DataSet como uma nova tabela, em vez de ser mesclada à tabela Customer.

Vamos assumir que você tenha nomeado tudo corretamente e que tudo esteja funcionando a contento. À medida que você continuar elaborando o código, precisará, em algum momento, alterar também o DataSet tipado. No meu exemplo, eu gostaria de alterá-lo para adicionar um campo de string a fim de refletir um novo campo existente no banco de dados. O único problema é que o campo no banco de dados é um inteiro. Se você executar esse código, obterá um erro em tempo de execução no método Merge. Mas esse problema é detectado facilmente.

O que acontece se você modificar o DataSet tipado e tentar uma atualização quando um dos tipos de dados estiver incorreto? Talvez você obtenha um erro como "Input string in incorrect format". Na verdade, eu recebi essa mensagem de erro e ela me levou à loucura, porque o DataSet tipado era extremamente complexo, com 25 ou mais elementos. O pior é que a mensagem de erro não informava onde o erro ocorria. Após muita procura, consegui localizar o erro imprimindo o schema dos DataSets tipados e comparando-o, campo por campo, com o esquema das tabelas. Encontrei uma discrepância na qual o elemento do DataSet e seu campo correspondente na tabela não coincidiam. Corrigi o DataSet tipado e tentei novamente. Dessa vez, funcionou.

Conforme mencionei anteriormente, existem muitas maneiras de se mover dados em um aplicativo, mas as necessidades de cada aplicativo podem ser diferentes. Uma boa idéia é desenvolver um protótipo dos diferentes componentes do seu código e solucionar as falhas de transferência de dados que surgirem pelo caminho. A partir daí, você poderá elaborar padrões para métodos diferentes ao mesmo tempo em que aprimora a sua base de conhecimento contra armadilhas em potencial.