|
|
 |
| Tecnologias :: Visual FoxPro |
 |
|
| |
|
| Consultando DataSets ADO.NET utilizando o Engine de dados do VFP |
Por Kamal Patel, artigo originalmente publicado na revista UTMag/RapoZine. |
Nivel de dificuldade
Este artigo está situado entre o nível intermediário para o avançado de desenvolvimento. supõe-se que os desenvolvedores tem capacidade de criar e utilizar um componente COM usando VFP, tem um conhecimento básico de ADO.NET e estão familiarizados com C# ou VB .NET.
A Microsoft introduziu ADO.NET como um novo modelo de acesso a dados através do .NET framework. Como a maioria dos novos modelos, ADO.NET tem algumas limitações. Neste artigo, discutirei uma limitação chave no ADO.NET e demostrarei como superar esta limitação utilizando a combinação de uma classe emcapsulada .NET e um componente COM do VFP
O Problema
Pesquisar cursores e manipular dados não relacionados localmente é uma característica do Visual FoxPro desde sua primeira implementação. Esta característica distribui dados de um banco de dados, permite modificar estes dados via cursores e opcionalmente mostrá-los em um formato mais apropriado. Então ? Infelizmente existem apenas algumas linguagens/tecnologias que suportam tais características no 'lado-cliente' (ref. a arquitetura cliente/servidor - nota do tradutor) ADO.NET não é exceção.
Acesso a dados em ADO.NET é manipulada por provedores de bancos de dados. O .NET Framework já vem com dois provedores; provedores para OLEDB e SQL SERVER. Usando estes provedores, os desenvolvedores podem estabelecer conexões com o banco de dados e recuperar os resultados. ADO.NET suporta dois mecanismos para capturar o resultado de pesquisas de um banco de dados: DATATABLE e DATAREADER. Os DataReaders apenas suportam integração de envio de dados e apenas permitem acesso read-only aos dados. Outro fator importante sobre os DataReaders é que eles necessitam uma conexão intermitente com o banco de dados. O DATATABLE é mais rico em funcionalidades. Os DATATABLES não requerem conexão intermitente com o back-end e suporta interação de envio e retorno de dados. Multiplos DATATABLES são geralmente armazenados em um DATASET. DATASETs são como os bancos de dados do VFP armazenados na memória.
Infelizmente, a atual arquitetura do ADO.NET, não permite executar instruções SQL em tabelas sem um DATASET. Então, como nós resolvemos este problema? Nós tivemos que escrever um pequeno programa para interagir através das linhas em um DATATABLE e gerar um novo resultado DATATABLE dinamicamente, ou, nós executamos uma nova pesquisa no servidor para criar um novo DATATABLE. O primeiro requer algum esforço, mas nos dará um resultado melhor. O segundo não é difícil de implementar mas requer uma conexão do banco de dados com o back-end. Isto significa que os aplicativos devem ter uma maneira de sempre se conectar ao banco de dados e isso não traz vantagens para os dados existentes na memória. Neste artigo eu criarei uma ferramenta que suportará pesquisas SQL nos DATATABLES em um DATASET
A Abordagem
O "desenho" é muito simples. Eu passo múltiplas strings XML para um componente VFP que converterá o XML em cursor VFP. Quando eu passo uma instrução SQL para executar o componente VFP. O componente então executa a pesquisa e retorna o resultado no formado XML a quem o requisitou.
Eu escolhi usar o VFP 7.0 para construir este componente porque ele suporta construtores que rodam pesquisas SQL em cursores de memória. O componente também atua em várias outras características no VFP tais como a compilação 'on the fly' (macro-expansão) e a serialização e deserialização do XML para cursores nativos do VFP.
Baseado nestas considerações, eu criei um componente VFP chamado CursorX, que baseado em uma classe 'SESSION'. Ela expõe dois métods públicos Add() e Execute(). o Add() recebe os dados XML e o nome do cursor como parâmetros e converte o XML em cursor utilizando a função XMLTOCURSOR(). O método Execute() recebe a instrução SQL e o nome do cursor resultado como parâmetros e é a responsável pela execução da pesquisa.
Figura 1.1: Mostra os métodos e propriedades na nossa classe CursorX
A função XMLTOCURSOR() do VFP requer que que o XML seja fornecido em um formato particular. Quando chamada, ela cria um cimples cursor a partir do XML fornecido. Se eu quero executar consultas contra múltiplos DataTables, eu preciso passar o XML para cada DataTable separadamente para o método Add()
Há uma outra limitação do ADO.NET que precisa ser contornada: o ADO.NET não é capaz de serializar um específico DataTabler partindo de um DataSet. O DataSet é aquele que implementa a interface IXMLSerialization, e provê serialização do DataSet de e para XML. Isto significa que para implementarmos a classe em VFP descrita acima, temos que encontrar uma forma de obter o XML para cada DataTable separadamente. Uma opção seria fazer o componente em VFP experto o suficiente para ler o XML e criar múltiplos cursores. Isto necessitaria alterar o método Add() para somente receber o XML como um parãmetro, e então fazer o parse do XML para criar um ou mais cursores baseados no XML fornecido.
Outra forma é deixar o componente VFP como está, e criar um "Decorador" que se responsabilizará por passar apropriadamente o XML formatado para o componente VFP. Eu gosto mais desta forma porque também tem algumas outras vantagens. Nós veremos a implementação de COM no VFP e então olharemos para o Decorados .NET e suas vantagens.
Implementação COM
O componente COM será responsável por:
- Converter os dados XML para cursores VFP
- Executar consultas
- Serializar o cursor resultante e retornar o XML
No meu exemplo, eu implemento este componente como uma classe chamada CursorX. O método Add() recebe um stream XML e um nome como parâmetros. Ele converte o XML em um cursor e mantém uma matriz privada com os nomes dos cursores para uso futuro. O método Execute() recebe a consulta SQL e o nome do novo cursor como parâmetros. Ele executa a consulta e converte o cursor em uma string XML. Feito, ele remove a codificação (encoding) do XML e retornar a string XML. Aqui está o código para a classe CursorX:
DEFINE CLASS CursorX As Session OLEPUBLIC
DIMENSION aCursors(1)
*-- Recebe um XML e o converte em cursor
PROCEDURE Add(tcXMLDataTable As String, tcCursorName As String)
*-- Converte o XML para string e a mantém
XMLTOCURSOR(tcXMLDataTable, tcCursorName)
*-- Mantém os valores atuais em uma matriz
LOCAL lnLength As Integer
lnLength = ALEN(this.aCursors) + 1
DIMENSION this.aCursors(lnLength, 2)
this.aCursors(lnLength, 1) = tcXMLDataTable
this.aCursors(lnLength, 1) = tcCursorName
ENDPROC
*-- Executa o comando SQL e retorna o XML resultante
FUNCTION Execute(tcSQL As String, tcResultDataTableName As String) As String
*-- Obtém um nome temporário para o cursor
LOCAL lcTempName As String
lcTempName = SYS(2015)
*-- Fecha caso ele já estiver em uso
IF USED(lcTempName)
USE IN (lcTempName)
ENDIF
IF USED(tcResultDataTableName)
USE IN (tcResultDataTableName)
ENDIF
*-- Constrói o comando SQL e o executa
tcSQL = tcSQL + " Into Cursor " + lcTempName
&tcSQL
*-- Truques antigos não morrem facilmente. Veja se consegue perceber o que é :)
USE DBF(lcTempName) AGAIN IN 0 ALIAS (tcResultDataTableName)
SELECT (tcResultDataTableName)
*-- Não desejamos retornar um XML em branco,
*-- então ao menos adicionamos 1 registro em branco
IF RECCOUNT(lcTempName) = 0
APPEND BLANK
ENDIF
*-- Converte os resultados para XML, remove a primeira linha e retorna o XML
LOCAL lcRetVal As String
lcRetVal = ""
CURSORTOXML(tcResultDataTableName, "lcRetVal", 1, 2, 0, "")
*-- Remove a primeira linha que especifica o XML
LOCAL nPos As Integer
nPos = AT(">", lcRetVal)
RETURN ALLTRIM(SubStr(lcRetVal, nPos + 3))
ENDFUNC
ENDDEFINE
Quando estava construindo o componente, dei o nome ao projeto VFP de "LocalDb", e compilei como uma MTDLL. Isto produziu a ProgId para o meu componente desta forma "LocalDb.CursorX".
O Decorador .NET
A classe ManagedCursorX (veja Figura 1.2) decora o objeto COM CursorX. Decora ambos os métodos Add() e Execute().
Figura 1.2: Métodos e propriedades expostas na classe decoradora ManagedCursorX.
Você provavelmente percebeu na Figura 1.2, que a diferença óbvia aqui é que existem múltiplos métodos Add() e o método Execute() recebe um parâmetro diferente do que aquele no VFP. Porque temos três métodos Add() na classe? Isto é válido? Sim, isto é válido nas classes do .NET Framework e isto é chamado "Method Overloading" (sobrecarga de método). Diferente do VFP, métodos criados nas classes do .NET Framework não usam o nome de método para identificar de maneira única cada método na classe. Ao invés disto os métodos têm "assinaturas" que são combinadas como o nome dos métodos e o tipo de dados dos parâmetros que entram para o método, e o número de parâmetros. No caso acima, o método Add() recebe diferente números de parâmetros e diferentes tipos de dados (DataTypers), e então eles todos são válidos. A implementação de cada método pode ser totalmente separada, contanto que seu valor de retorno seja do mesmo tipo de dado. Baseado nos parâmetros que entram a classe automaticamente determina qual método deve chamar.
Aqui está a implementação da classe ManagedCursorX:
Imports System.Data
Imports System.IO
Imports System.Xml
Imports localdb
Public Class ManagedCursorX
' Cria uma referência para o objeto do VFP CursorX na
' inicialização
Private oRefCursorX As localdb.CursorX
Public Sub New()
oRefCursorX = New localdb.CursorX()
End Sub
' Recebe um DataSet como um parâmetro para cada DataTable
' envia a chamada para converter o XML em cursor
Public Sub Add(ByVal toDataSet As DataSet)
' Faz iteração através de cada tabela no DataSet
Dim oTable As DataTable
For Each oTable In toDataSet.Tables
' Chama o método "sobrecarregado" (overloaded) para fazer a adição de DataTable
Me.Add(oTable)
Next
End Sub
' Recebe um DataTable como parâmetro, e envia para a
' chamada de conversão do XML em cursor
Public Sub Add(ByVal toDataTable As DataTable)
' DataSet temporário
Dim TempDataSet As New DataSet()
' Adiciona a tabela temporariamente ao DataSet temporário
TempDataSet.Tables.Add(toDataTable.Copy())
' Adiciona a representação XML do DataTable para a coleção
Me.Add(TempDataSet.GetXml(), toDataTable.TableName)
' Remove a tabela quando estiver pronto
TempDataSet.Tables.Remove(toDataTable.TableName)
End Sub
' Recebe uma string de dados XML e nome de Cursor como parâmetro e
' envia a chamada para converter XML em cursor
Public Sub Add(ByVal tcXMLDataTable As String, ByVal tcCursorName As String)
oRefCursorX.Add(tcXMLDataTable, tcCursorName)
End Sub
' Recebe a consulta SQL, um nome de DataTable para armazenar os resultados desta consulta
' e uma referência a um DataSet para adicionar a recém-criada DataTable
' A consulta é executada e o DataTable é adicionado ao DataSet
Public Sub Execute(ByVal tcSQL As String, ByVal tcResultDataTableName As String, _
ByRef oDataSet As DataSet)
Dim lcResult As String
lcResult = oRefCursorX.Execute(tcSQL, tcResultDataTableName)
'Cria o DataSet temporário aqui
Dim ds As New DataSet()
Dim RDR As XmlTextReader = New XmlTextReader(lcResult, XmlNodeType.Document, Nothing)
ds.ReadXml(RDR)
'Checa se o resultado existe no DataSet e se existir, o remove
If Not oDataSet.Tables(tcResultDataTableName) Is Nothing Then
'Remove a tabela existente
oDataSet.Tables.Remove(tcResultDataTableName)
End If
'Adiciona a nova tabela resultante ao DataSet atual
oDataSet.Tables.Add(ds.Tables(0).Copy())
End Sub
End Class
Anteriormente eu mencionei que o método Execute() recebe parâmetros diferentes neste caso. Aqui o método Execute() não recebe o nome do resultado como segundo parâmetro, mas ao invés disto recebe uma referência a um objeto DataSet. Desta forma, o Decorador pode agora tomar a responsabilidade de adicionar o DataTable resultante ao DataSet existente. Outra vantagem de usar o Decorator é a flexibilidade de passar vários tipos do .NET para o método Add(), sem se procupar em serializá-los em XML.
Consultando os DataSets ADO.NET
Agora que construímos o alicerce de nossa ferramenta, vamos ver a utilização da ferramenta executando consultas nos DataSets. Vamos dizer que temos um DataSet, ds, que contém duas DataTables, Customers (Clientes) e Orders (Pedidos). Neste exemplo, nós executaremos uma consulta para obter o total de vendas para os clientes:
' Criar uma nova instância de ManagedCursorX
Dim oCursorX As New ManagedCursorX()
' Adicionar todos os DataTables Customer e Orders deste DataSet e fazê-los disponíveis para consulta
oCursorX.Add(ds)
' Executar uma query em DataTables no *DataSet* e obter as vendas para o cliente
oCursorX.Execute("Select Customer.Company, sum(Orders.Sales) from Orders " + _
"where Customer.cust_id = Orders.cust_id group by Customer.Company", "CustomerSales", ds)
'Opcionalmente escolher mostrar estes resultados em um grid
myDataGrid.DataSource = ds.Tables("CustomerSales").DefaultView
Com algumas simples linhas de código, o ADO.NET agora suporta um acesso baseado em conjunto com acesso através do Engine de Dados do VFP. No exmeplo acima nós o método Add() passando o DataSet. O método Add() é sobrecarregado e também suporta adicionar DataTables e strings XML. Se formos consultar um XML serializado de um arquivo MetaData chamado LocalizedStrings.xml para extrair todos os locais dos USA nosso código poderia ser:
' Estamos usando FileToStr() do VFP Toolkit, então importamos aquela biblioteca
Imports VFPToolkit.strings
' Criar uma nova instância de ManagedCursorX
Dim oCursorX As New ManagedCursorX()
' Adiciona tabelas serializadas em string chamada "LocalizedStrings.xml", com um alias de "Locales"
oCursorX.Add(FileToStr(Application.ExecutablePath + "\LocalizedStrings.xml"), "Locales")
Dim ds As New DataSet()
' Executar consulta para country = USA, criar um novo DataTable chamado US_Locales
oCursor.Execute("Select * from Locales where country = [USA]", "US_Locales", ds)
Conclusão
Há duas limitações principais no ADO.NET. Primeiro, a classe DataTable não suporta nativamente a serialização de XML. Segundo (e mais importante), o DataSet não suporta acesso basedo em conjunto (set-based).
O componente que criamos é muito básico, mas tem méritos da rápida execução das poderosas características no VFP na exeução de consultas em DataSets em ADO.NET. Isto também significa que podemos usar a maioria dos comandos do VFP em instruções SQL a partir do .NET se necessário. Algo como Select IIF(EMPTY(name), "-", ALLTRIM(name)) as name from customer em um DataSet contendo um DataTable chamado "Customer". Funciona muito bem.
Embora nós soluciamos o problema, alguém poderia perguntar... "Esta é a forma correta de solucionar o problema de consultar DataTables em DataSets?". Na ausência de um provedor para DataSets no ADO.NET, isto é muito melhor do que nada. Vamos esperar que algum dia, alguém venha com um provedor para DataSets no ADO.NET, permitindo aos desenvolvedores não somente executar comandos Select-SQL, mas também insert, update e deletes em DataSets na memória.
Kamal Patel é um Arquiteto de Solução e Desenvolvedor Senior com a GoAmerica Communications. Kamal é especialista em projeto de aplicações e desenvolvimento e focaliza seus esforços em intranet, dispositivos móveis e aplicações de negócios, usando o Visual Studio, .NET Framework, C#, ASP.NET, VB .NET, XSLT e WML. Kamal é palestrante na indústria de conferências e fala frequentemente nos grupos de usuários de NY e NJ. Kamal é o Desenvolvedor-Lider do Visual FoxPro Toolkit for .NET e é o autor do Web Service ConvertCSharpToVB. Ele vem escrevendo vários artigos, tem graduação em Ciências da Computação e é um Microsoft Certified Solution Developer (MCSD) desde 1998. Você pode contactá-lo via email em kppatel@yahoo.com. (http://www.KamalPatel.net)
topo
|
|
 |