Microsoft.com Brasil Home | Mapa do Site
HyperLink
 
Procurar no Microsoft.com por:
 
  Home | Developer Center | Biblioteca | Downloads | Como Comprar | Assinaturas MSDN




Pesquisa rápida

 
 
  
Home MS Brasil
Compre
Downloads
Suporte
Fale conosco

 Tecnologias :: Visual FoxPro
 
  
Faça o download deste documento:
Ensaio do VFP com ADO.NET
 formato Word - 339 Kb

Ensaio do VFP com ADO.NET


Por Marco Antonio Mazzarino, artigo originalmente publicado na revista UTMag/RapoZine.






Com o advento da tecnologia .NET o Visual FoxPro foi retirado do conjunto de produtos do Visual Studio .NET. Isto tem causado certa apreensão junto aos desenvolvedores que são usuários do VFP. Resta-nos a esperança de que o time do FoxPro da Microsoft coloque o produto alinhado com esta nova tecnologia de software e com isso reintegra-lo novamente àquele pacote de ferramentas de desenvolvimento de aplicação.

Como é sabido, o .NET Framework se apóia no .NET Runtime ou CLR (Common Language Runtime) para executar módulos gerenciados (Managed Code) a partir de uma linguagem intermediária (IL - Intermediate Language), que é produzida pela compilação do módulo fonte. A partir desse módulo o CLR faz o estágio final de execução da compilação chamada de JIT - Just-in-time compilation.

Como o interpretador do Visual FoxPro usa código objeto nativo, sua poderosa linguagem não pode ser executada nessa nova tecnologia. Até que isto venha acontecer, podemos fazer uso de alguns artifícios e promover a integração do FoxPro com .NET Framework via COM (Component Object Model), pois o VFP não pode "instanciar" diretamente um objeto do .NET Framework. Isso resultará um código híbrido, mas que, como veremos, cumpre perfeitamente a sua finalidade para a inter-operatividade entre o código nativo e o gerenciado.

Para que esta integração seja possível, faremos uso de uma pequena DLL (COM), escrita em C# (veja o código fonte a seguir), para expor os serviços do .NET Framework para o VFP. Ela é fundamental para que o VFP possa consumir dados usando o ADO.NET. Vale ressaltar ela tem o enfoque apenas didático, e seu uso profissional requer adaptações mais consistentes.

Dentre as novidades advindas do .NET Framework, o ADO.NET se apresenta para ser o novo paradigma de acesso à dados. O ADO.NET é uma biblioteca de código gerenciado, construída pela Microsoft, a partir do ponto zero, que a rigor têm semelhança com o ADO, apenas em seu nome.

Concluída a DLL (veja no final deste artigo os passos para montá-la de modo que o VFP possa acessá-la), a primeira etapa é obter um objeto do COM:

oNet = CREATEOBJECT("dbrollsistemas.cadastro.northwind")

"dbrollsistemas.cadastro" é o nome do namespace que incorpora a classe northwind que expõe para o VFP as funcionalidades de acesso ao Banco de Dados SQL-Server Northwind usando o ADO.NET. Ela tem um conjunto de métodos públicos, especializados em resolver algumas questões que o VFP não consegue, fazendo-os diretamente com o .NET Framework.

O objeto oNet, devolvido pela nossa DLL, é a porta de entrada para o novo mundo.

O ADO.NET é bastante complexo e apresenta várias formas de tratamento de informações, com diferenças bastante evidentes em relação ao ADO. A principal, é que ela privilegia operações desconectadas do servidor de dados. Isto é um ganho, já que a utilização de recursos estão sob o seu controle e os dados estão independentes para serem usados com maior flexibilidade.

Dentre os vários enfoques para tratar dados, vamos nos basear no conceito de DataSet, que nos é oferecido pelo ADO.NET, embora haja outras formas também complexas, como DataView, DataReader, sem falar no conjunto de recursos bastante extenso para XML.

O DataSet é um "cluster", que acondiciona um conjunto de tabelas (DataTable), que fazem uso de coleções de linhas (DataRow) para representar os dados e de colunas (DataColumn) para representar o esquema para mapeá-los (metadados). Esses componentes são objetos, portanto acessíveis pelo VFP. Com a combinação de métodos e de propriedades, o VFP terá acesso a um grande número de recursos do ADO.NET.

A classe Northwind

A nossa DLL contém apenas uma classe chamada de NorthWind e fará uso de apenas duas das tabelas do banco de dados Northwind do SQL-Server: Customers e Orders. Ela tem três propriedades públicas, com valores default, mas que podem ser personalizadas para flexibilizar a criação do DataSet:

source = "server=localhost,uid=sa;pwd=;database=northwind";
clientes = null;
pedidos = null;


Você pode melhor explicitar a coleção de dados alterando o contexto da pesquisa. Por exemplo:

oNet.clientes = "SELECT * FROM Customers"
oDS = oNet.CriarDataSet("Customers",oNet.clientes)
oNet.pedidos = "SELECT * FROM Orders WHERE ShipCountry = 'Brazil'"
oNet.CriarDataSet("Orders",oNet.pedidos)


Com este último serviço nosso DataSet passou a contar com duas coleções de tabelas: uma chamada Customers e outra chamada Orders. Observe que na chamada para o método CriarDataSet(...), você tanto pode usar como parâmetros, diretamente o texto para a execução do SELECT, como salva-lo nas propriedades clientes para a tabela Customers e na propriedade pedidos para a tabela Orders e usa-las como parâmetro.

Você pode confirmar a quantidade de tabelas do seu DataSet teclando:

? oDS.Tables.Count

e você terá como resultado: 2 (Dois)

O DataSet

Tables é uma coleção de tabelas de um DataSet, ou seja uma coleção de objetos que apontam para suas tabelas respectivamente. Para que possamos tratá-las discretamente, vamos obter os objetos de cada uma das tabelas, distintamente:

oDC = oNet.tabelas("Customers")
oDO = oNet.tabelas("Orders")


tabelas(...) é um método do nosso COM, que nos devolve o objeto de cada tabela. Você pode obter esses objetos de várias formas: pelo uso da estrutura FOR EACH do VFP ou diretamente do ADO.NET, mas o nosso método é mais eficiente, como você pode constatar a seguir:

Usando a estrutura FOR EACH

FOR EACH table IN oDS.Tables
? table.GetType.ToString(),table.ToString
ENDFOR


O resultado será:

System.Data.DataTable Customers
System.Data.DataTable Orders


O mesmo resultado poderá ser obtido diretamente do ADO.NET

oDC = oDS.Tables.Item(0)
oDO = oDS.Tables.Item(1)


Para confirmar tecle:

? oDO.GetType.ToString(),oDO.ToString

Observe que as três alternativas nos darão resultados semelhantes, entretanto o nosso COM é mais eficiente na medida em que seu uso é mais amigável, fazendo o seu código mais elegante e melhor documentado.

O DataTable

A classe DataTable pode nos dar duas coleções importantes para o acesso aos dados: a primeira delas é o DataColumn, que define a estrutura de dados da tabela. Trata-se de um metadados, que define os atributos de cada uma das colunas da nossa tabela; a outra é o DataRow que define a linha de dados da tabela.

O DataColumn é apontada pela propriedade Columns que é uma coleção de classes. Usando a estrutura FOR EACH, vamos examinar a estrutura de dados da tabela Customers que nos foi oferecida pelo ADO.NET:

FOR EACH Col IN oDC.Columns
? col.columnName,col.DataType.Name(),col.DefaultValue,;
col.MaxLength
ENDFOR


O resultado será:

CustomerID String .NULL. -1
CompanyName String .NULL. -1
ContactName String .NULL. -1
...


O DataRow é apontada pela propriedade Rows que também é uma coleção de classes: repetindo a estrutura a estrutura FOR EACH vamos obter a lista de dados fornecida na tabela Customers pelo ADO.NET:

FOR EACH row IN oDC.Rows
? row.Item(0),row.Item(1)
ENDFOR


O resultado poderá ser:

ALFKI Alfreds Futterkiste
ANAKTR Ana Trujillo Emparederos y helados
...
WOLZA Wolsky Zajazd


Aqui acontece algo interessante: A forma que o ADO.NET se apresenta para acessar as colunas de dados individualmente é através da propriedade Item. Essa propriedade na verdade é um indexador (indexer) que é uma novidade da linguagem C#. O VFP não pode usar este "indexer" diretamente, mas pode informar um índice. Esta é a razão pela qual o usamos para obter os dados das colunas, da maneira feita acima.

Como trabalhar com índices não é amigável, vamos usar o serviço do nosso COM para obter esse índice. É o método indices(...), que recebe os parâmetros nome da tabela e nome da coluna e nos devolve um índice correspondente àquela coluna:

? oNet.indices("Customers","City")

ou

? oNet.indices(oDC,"City")

o resultado será: 5

A contrapartida é o método colunas(...) que nos devolve o objeto DataColumn daquela coluna:

oCol = oNet.colunas("Customers",1)

ou

oCol = oNet.colunas("Customers","CompanyName")

O objeto oCol representa o esquema da tabela Customers:

? oCol.GetType.ToString()

o resultado será : System.Data.DataColumn
Ou ainda:

? oCol.ToString()

o resultado será: CompanyName
O mesmo resultado também será obtido usando-se o método nomecoluna(Nome da Tabela, Ordinal):

? oNet.nomecoluna("Customers",1)

Adicionar uma linha de dados à nossa coleção
A classe DataTable pode nos dar um objeto (stand alone) do tipo DataRow para que possamos tratar uma linha de dados fora de sua coleção de dados. Após preenchê-la com as nossas informações poderemos devolvê-la para a coleção da tabela e assim acrescentar uma nova linha, àquela:

oRow = oDC.NewRow()

Vamos preenchê-la com dados usando o índice do ADO.NET:

WITH oRow
.item(oNet.Indices("Customers","CustomerId")) = "DBROL"
.item(oNet.Indices("Customers","CompanyName")) = ;
"dbRoll Sistemas e Adm Dados"
...
.item(oNet.Indices("Customers","Country")) = "Brasil"
.item(oNet.Indices("Customers","Phone")) = "(5511) 3862-6660"
ENDWITH

Quando a linha estiver preenchida com os dados, podemos devolvê-la para ser incluída na coleção da tabela Customers:

oDC.Rows.Add(oRow)

É quando recebemos uma mensagem de erro como esta:

"OLE Error Code 0x80004002: Não há suporte para esta interface"

Esta é uma questão intrigante.
Uma análise mais detalhada revela que não cometi nenhum erro: Rows é uma propriedade que contém um objeto da classe DataRowCollection, que têm um método Add(), que aceita como parâmetro um DataRow. Se todas as condições estão corretas, por que esta mensagem?

A resposta pode ser que o VFP não resolve adequadamente os métodos do tipo "overloaded" , quando acionados diretamente no .NET Framework. O método Add() tem vários protótipos.

Para contornar este entrave, vamos recorrer ao nosso COM. O método AddRow(DataTable,Row) faz o serviço de inclusão da linha de dados na coleção da tabela:

oNet.AddRow(oDC,oRow)

Relacionamento entre tabelas do DataSet

A grande novidade, entre muitas, do ADO.NET é que podemos estabelecer um relacionamento entre a nossa coleção de tabelas no DataSet da mesma forma que o fazemos com o Banco de Dados. Isto é um ganho significativo em relação ao ADO que não faz este tipo de estrutura hierárquica no recordset. Dessa forma podemos estabelecer uma relação entre as tabelas Customers e Orders da mesma maneira que está estabelecida no banco de dados Northwind através primary key com o foreign key das colunas CustomerId de ambas as tabelas.

O ADO.NET faz isso pelo método Add() da classe DataRelationCollection, apontada pela propriedade Relations. Como o método Add() é "overloaded" e se a chamada for direta ao ADO.NET, vamos receber a mensagem acima. O nosso COM tem um método AddRelation(...) que faz isso para nós. Este método é um espelho de um dos "overloads" da classe DataRelationCollection;

Ele aceita quatro parâmetros, o objeto DataSet, o nome da relação, o objeto da coluna pai e o objeto da coluna filho:

oColCustomer = oNet.colunas("Customers","CustomerId")
oColOrder = oNet.colunas("Orders","CustomerId")
oNet.AddRelation(oDS,"CustomerOrder",oColCustomer,oColOrder)

Para conferir tecle:

oRel = oDS.Relations.Item(0)
? oRel.ToString()

e você terá: CustomerOrder
o objeto oRel, nos fornece uma série de informações sobre a relação:

? oRel.ParentTable.ToString(),' -> ',oRel.ChildTable.ToString()

resultado: Customers -> Orders

oParent= oRel.ParentColumns
? "Colunas do Pai: "
FOR EACH col IN oParent
?? col.toString()
ENDFOR

oChild= oRel.ParentColumns
? "Colunas do Filho: "
FOR EACH col IN oChild
?? col.toString()
ENDFOR

e você terá:

Colunas do Pai: CustomerId
Colunas do Filho: CustomerId

Agora que temos a relação, vamos incluir uma nova linha na tabela Orders que esteja relacionada com uma linha da tabela Customers, que foi incluída anteriormente. Para isso vamos criar uma Função especializada no VFP:

FUNCTION NewOrder(oTableOrder AS Object) AS void
LOCAL oRow
oRow = oTableOrder.NewRow()
WITH oRow
.item(oNet.indices("Orders","OrderId")) = 9999
.item(oNet.indices("Orders","CustomerId")) = "DBROL"
.item(oNet.indices("Orders","EmployeeId")) = 8888
.item(oNet.indices("Orders","OrderDate")) = DATETIME()
.item(oNet.indices("Orders","ShippedDate")) = ;
DATETIME() + 2 * (60 * 60 * 24)
.item(oNet.indices("Orders","ShipVia")) = 1
.item(oNet.indices("Orders","Freight")) = 1325.40
.item(oNet.indices("Orders","ShipName")) = "Lonei Calçados"
.item(oNet.indices("Orders","ShipAddress")) = ;
"Shopping Vila Lobos - Loja 355"
.item(oNet.indices("Orders","ShipCity")) = "São Paulo"
.item(oNet.indices("Orders","ShipRegion")) = "1"
.item(oNet.indices("Orders","ShipPostalCode")) = "05477000"
.item(oNet.indices("Orders","ShipCountry")) = "Brasil"
ENDWITH
oNet.AddRow(oTableOrder,oRow)
ENDFUNC

Este modelo de função é valido para o VFP versão 7. Para as versão anteriores use o formato tradicional.

Para incluir a nova linha:

NewOrder(oDO)

Para que você possa melhor visualizar o que fizemos até aqui, vamos obter uma lista da nossa relação com o nome de um cliente com os respectivos pedidos. A maneira mais pratica de obter esta lista é fazer uso da estrutura FOR EACH do VFP. Lembre-se que as nossas tabelas são coleções de linhas.

A classe DataRow tem um método chamado GetChildRows() que nos devolve uma coleção dos filhos de uma relação. Como este método é "overloaded", teremos recorrer ao nosso COM, com um método espelho, que recebe dois parâmetros, o objeto DataRow do pai e o objeto DataRelation. Ele nos devolve uma coleção de DataRow com os dados do filho.

oChild = oNet.GetChildRows(myRow,myRelation)

Se você teclar o comando acima ele não vai funcionar. Para que você tenha um panorama mais apropriado desta lista, vamos definir uma função VFP especializada para fazer esta lista:

FUNCTION ListaRelacao()
For Each myRelation in oDC.ChildRelations
For Each myRow In oDC.Rows
oChild = oNet.GetChildRows(myRow,myRelation)

IF oChild.Count > 0
? oNet.valor(myRow,"CompanyName")
?? SPACE(1)+oNet.valor(myRow,"Phone")
FOR EACH oRow IN oChild
? "..."
?? oNet.valor(oRow,"CustomerId")+ SPACE(5)
?? TTOD(oNet.valor(oRow,"OrderDate"))
?? SPACE(1)
?? TTOD(oNet.valor(oRow,"ShippedDate"))
?? SPACE(1)+oNet.valor(oRow,"ShipCity")+ SPACE(5)
?? TRANSFORM(oNet.valor(oRow,"Freight"),"99,999.99")
?? SPACE(1)+oNet.valor(oRow,"ShipName")+SPACE(1)
?? SPACE(1)+oNet.valor(oRow,"ShipAddress")+SPACE(1)
ENDFOR
ENDIF
ENDFOR
ENDFOR
ENDFUNC

Tecle

ListaRelacao()

e o resultado poderá ser:

Comercio Mineiro (5511) 555-56747 ...COMMI 27/08/1996 03/09/1996 Sao Paulo 79.70 Comercio Min... ...COMMI 06/03/1997 13/03/1997 Sao Paulo 11.93 Comercio Min... ...

dbRoll Sistemas e Adm Dados (11) 3862-6660 ...DBROL 11/02/2002 13/02/2002 Sao Paulo 1,325.40 Lonei Calç...

Observe que nosso registro recém incluído aparece na nossa lista.

O método valor(...) do nosso COM foi usado para se obter o dado da coluna. Este método recebe dois parâmetros, o objeto DataRow e o string com o nome da coluna. O VFP recebe este valor no formato apropriado em função do tipo contido no esquema de dados da tabela.

Outra maneira de se obter este valor é usar os métodos do próprio ADO.NET:

oRow = oDC.Rows.item(oDC.Rows.count-1)
? oRow.item(0)
? oRow.item(1) ...

ou combinar ambos:

oRow = oDO.Rows.item(oDO.Rows.count-1)
? oRow.item(oNet.indices(oDO,"CustomerId"))
? oRow.item(oNet.indices(oDO,"OrderDate")) ...

O estado e a versão do rowset

O mapeamento das linhas e das colunas na nossa coleção de informações, é chamada de rowset.

A linha de informação de um rowset tem uma vida latente. Isto pode ser percebido na propriedade RowState da classe DataRow. É algo equivalente a função GETFLDSTATE() do VFP.

Os estados são definidos por uma estrutura Enumerator chamada DataRowState, que não é reconhecida pelo VFP. A melhor forma de simular o Enumerator é usar uma lista de defines para cada um dos atributos do RowState:

#DEFINE Added 0x04
#DEFINE Deleted 0x08
#DEFINE Detached 0x01
#DEFINE Modified 0x010
#DEFINE Unchanged 0x02

Por exemplo, a partir da nossa linha que foi recém incluída no rowset podemos estabelecer um monitoramento sobre seu estado. Isto é particularmente útil quando estamos tratando com uma aplicação multi-usuária, onde o monitoramento de estado é bastante importante para a integridade da informação:

oRow = oDC.Rows.Item(oDC.Rows.Count-1)

vai nos dar a última linha do nossa coleção de Rows da tabela Customers

? oRow.RowState

o resultado será: 4 (equivalente ao Enumerator Added)

Para testar a linha anterior:

oRow = oDC.Rows.Item(oDC.Rows.Count-2)
? oRow.RowState

o resultado será: 2 (Unchanged)

Vamos, agora usar a mesma linha para uma alteração:

oRow.Item(oNet.Indices("Customers","Country")) = "Polonia"
? oRow.RowState

o resultado será: 16 (Modified)

Se você obtiver a última linha da tabela Orders e a exclui-la:

oRow = oDO.Rows.Item(oDO.Rows.Count-1)
oRow.Delete()
? oRow.RowState

o resultado será: 1 (Detached)

Creio que os estados (state) da linha são auto-explicáveis. A questão desagradável é o código não muito amigável. Podemos desenvolver uma função no VFP que faça essa tradução com melhor compreensão:

FUNCTION RowState(oRow as Object) as string
#DEFINE Added 0x00000004
#DEFINE Deleted 0x00000008
#DEFINE Detached 0x00000001
#DEFINE Modified 0x00000010
#DEFINE Unchanged 0x00000002

RETURN ;

IIF(oRow.RowState = Added,"Added",;
IIF(oRow.RowState = Deleted,"Deleted",;
IIF(oRow.RowState = Detached,"Detached",;
IIF(oRow.RowState = Modified,"Modified","Unchanged"))))
ENDFUNC

Assim

? RowState(oRow)

resultará na string: "Detached"

Além do estado de linha, temos a versão da coluna. Ela representa os vários valores das camadas entre as operações advindas do DataSet. É algo equivalente as funções CURVAL() e OLDVAL() do VFP.

Da mesma forma que o RowState, ela faz uso do Enumerator DataRowVersion que pode ser traduzido pelos seguintes defines no VFP:

#DEFINE Current 0x0200
#DEFINE Default 0x0600
#DEFINE Original 0x0100
#DEFINE Proposed 0x0400

Para se testar a versão, usamos o método HasVersion(...) da classe DataRow. Você deve informar qual o tipo de versão de dado que você deseja, usando uma das quatro opções acima. Por exemplo, vamos voltar à penúltima linha da tabela Customers, aquele que alteramos o país para "Polonia", logo acima, e em seguida verificar a versão da tabela:

oRow = oDC.Rows.Item(oDC.Rows.Count - 2)
? oRow.HasVersion(0x200) ou oRow.HasVersion(Current)

a resposta será: .T.

? oRow.HasVersion(0x400) ou ? oRow.HasVersion(Proposed)

a resposta será: .F.

Para que você possa ter um panorama deste quadro, vamos definir uma função do VFP que nos devolverá um string, com as versões que estão ativas nessa linha:

FUNCTION RowVersion(oRow as Object) as String
#DEFINE Current 0x00000200
#DEFINE Default 0x00000600
#DEFINE Original 0x00000100
#DEFINE Proposed 0x00000400

LOCAL RowVersion
RowVersion = SPACE(0)
RowVersion = RowVersion + IIF(oRow.HasVersion(Current),"Current,",SPACE(0))
RowVersion = RowVersion + IIF(oRow.HasVersion(Default),"Default,",SPACE(0))
RowVersion = RowVersion + IIF(oRow.HasVersion(Original),"Original,",SPACE(0))
RowVersion = RowVersion + IIF(oRow.HasVersion(Proposed),"Proposed,",SPACE(0))
RETURN LEFT(RowVersion,RAT(',',RowVersion)-1)
ENDFUNC

Se você teclar:

? RowVersion(oRow)

o resultado poderá ser: Current,Default,Original

Neste caso, isto significa que na linha atual as versões Current, Default e Original estão disponíveis para você, ou seja, você pode acessar seus conteúdos em cada uma das colunas da sua linha. O VFP não pode fazer isso diretamente, (lembra-se do overloaded), então vamos usar um método do nosso COM que faça isso por nós. É o método versao(...) que aceita tres parâmetros: o objeto DataRow, o índice da coluna, e a versão desejada:

? oNet.Versao(oRow,0,0x200)

ou

? oNet.Versao(oRow,oNet.Indices("Customers","CustomerId"),Current)

o resultado poder ser: WOLZA

Vamos tornar este panorama ainda mais belo. Uma função em VFP que liste o estado (state) combinado com versão (version) de cada coluna da nossa linha, e seu respectivo conteúdo:

FUNCTION ListarVersao(oRow as Object) as VOID
#DEFINE Current 0x00000200
#DEFINE Default 0x00000600
#DEFINE Original 0x00000100
#DEFINE Proposed 0x00000400

? "NomeColuna: Estado, "+ RowVersion(oRow)

FOR EACH Col IN oRow.Table.Columns
? Col.ColumnName+":", RowState(oRow) , ",", ;
IIF(oRow.HasVersion(Current),oNet.Versao(oRow,Col.Ordinal,Current),SPACE(0)),",",;
IIF(oRow.HasVersion(Default),oNet.Versao(oRow,Col.Ordinal,Default),SPACE(0)),",",;
IIF(oRow.HasVersion(Original),oNet.Versao(oRow,Col.Ordinal,Original),SPACE(0)),",",;
IIF(oRow.HasVersion(Proposed),oNet.Versao(oRow,Col.Ordinal,Proposed),SPACE(0))
ENDFOR
ENDFUNC

Para acioná-la tecle:

ListarVersao(oRow)

o resultado poderá ser:

NomeColuna: Estado, Current, Default, Original Customerid: Modified, WOLZA, WOLZA, WOLZA ... Country: Modified, Polonia, Polonia, Poland ...

Observe que a versão Original da linha Country mantém o conteúdo Poland, enquanto as demais, contém Polonia.

O objeto oRow (da classe DataRow) disponibiliza vários métodos para que possamos operar a linha de dados. Vamos analisar algumas delas:

BeginEdit()
EndEdit()
CancelEdit()
AcceptChanges()
RejectChanges()
Delete()

Estes métodos praticamente são auto-explicativos. AcceptChanges() tem uma particularidade especial, pois ele faz o commit das suas edições de dados no DataSet, mas não na base de dados.

Por exemplo, para se fazer uma série de alterações na sua linha, inicie com o método BeginEdit()

oRow.BeginEdit()
oRow.item(oNet.Indices(oRow.Table,"PostalCode")) = "01000222"

para monitorar esta mudança, vamos usar nossa função:

ListarVersao(oRow)

o resultado poderá ser:

NomeColuna: Estado, Current, Default, Original, Proposed Customerid: Modified, WOLZA, WOLZA, WOLZA, WOLZA ... PostalCode: Modified,01-012,01000222,01-012,01000222 Country: Modified, Polonia, Polonia, Poland,Polonia ...

Observe a sutil diferença entre as versões Current e Original nas colunas PostalCode e Country nos assinalamentos de valores, antes e após o uso do BeginEdit(). Após explicitamente iniciado, o bloco de alterações mantém o conteúdo na versão Current e faz uso apenas da versão Proposed para os novos valores.

Leia a atentamente a documentação do .NET Framework para conhecer melhor as várias alternativas de usos desses métodos.

Neste ponto você tem a opção de cancelar sua edição com o método CancelEdit() ou encerrá-la com o método EndEdit(). Entretanto vamos radicalizar e deletar a linha:

oRow.Delete()
ListarVersao(oRow)

o resultado poderá ser:

NomeColuna: Estado, Current, Default, Original, Proposed Customerid: Deleted, , , WOLZA ... PostalCode: Deleted, , ,02-012 Country: Deleted, , ,Poland ...

Repare que foram mantidos os valores na versão Original.

Por fim você pode aceitar as mudanças ou rejeitá-la; vamos rejeitá-la:

oRow.RejectChanges()
ListarVersao(oRow)

o resultado será nossa linha original antes de qualquer alteração, inclusive com o estado Unchanged.

Quando incluímos a nossa linha em Customers, a coluna Country contém o valor "Brasil". Temos que alterá-la para "Brazil" para que ela ganhe a importância devida:

oRow = oDC.Rows.Item(oDC.Rows.Count-1)
oRow.BeginEdit
oRow.Item(oNet.Indices(oRow.Table,"Country")) = "Brazil"
oRow.EndEdit()
ListarVersao(oRow)

o resultado poderá ser:

NomeColuna: Estado, Current, Default, Original Customerid: Added, DBROL, DBROL, DBROL ... Country: Added, Brazil, Brazil, , ...

Como a nossa linha tem estado Added não existe valores no estado Original. Uma curiosidade, as linhas com o estado Detached não são passíveis de recuperação, pois eles não tem valores originais. É o caso de nossa linha Orders que deletamos acima. Observe a sutil diferença entre ambos os casos.

Conciliação dos dados no data base

Ainda nos resta conciliar nosso DataSet na sua base de dados de origem. Aquí o procedimento é muito semelhante ao ADO: primeiramente solicitamos uma conexão, fazemos sua abertura, comandamos o Update, fechamos a conexão. Nesse intermeio podemos usar a transação e votar por Commit ou RollBack, dependendo do resultado da operação. Este ensaio não vai tratar de Transações.

Inicialmente, vamos obter a conexão. Como o VFP não "instancia" objetos diretamente do .NET Framework, vamos usar nosso COM.

oCon = oNet.Conectar()

Para conferir:

? oCon.ToString()

o resultado: System.Data.SqlClient.SqlConnection

Em seguida vamos abrir a conexão:

oCon.Open()

Para que possamos proceder a alteração física dos dados, o ADO.NET exige a presença do DataAdapter para fazer o link com o método de acesso (Provider) que você está usando. Lembre-se, diferentemente do recordset, o DataSet funciona stand alone e o ADO.NET usará as informações de versões e estado combinados com o DataAdapter para realizar esta operação.

Nosso COM tem o método Adaptador(...) para obtenção do objeto do DataAdapter. Ele recebe dois parâmetros. O texto do Select e o objeto da conexão:

oDA = oNet.Adaptador(oNet.Clientes,oCon)
? oDA.ToString()

o resultado: System.Data.SqlClient.SqlDataAdapter

O DataAdapter permite você fazer a conciliação de várias formas, mas existe uma classe que automatiza este processo de uma forma mais simplificada, quando o nosso assunto for DataSet: é o SqlCommandBuilder. Veja a documentação do .NET Framework, para conhecer as outras opções de conciliação de dados. O nosso COM tem um método chamado Commando(...) que nos fornece este objeto, associando-o com o nosso DataAdapter:

oCB = oNet.Comando(oDA)
? oCB.ToString()

o resultado: System.Data.SqlClient.SqlCommandBuilder

Por fim podemos fazer nossa conciliação: ela é feita pelo método Update do DataAdapter. Como esse método é "overloaded" teremos que usar o nosso COM. O método Atualizar(...) recebe como parâmetros o objeto DataAdapter, o objeto DataSet e o nome da tabela:

QtdeRegistrosAfetados = oNet.Atualizar(oDA,oDS,"Customers")
? QtdeRegistrosAfetados

o resultado será: 1 (Um)

Sim, apenas a nova linha em Customers "DBROL" estava qualificada. A linha do cliente WOLZA teve a alteração rejeitada.

Para conciliar a tabela Orders:

oDA = oNet.Adaptador(oNet.Pedidos,oCon)
? oNet.Atualizar(oDA,oDS,"Orders")
oCon.Close()

o resultado será: 0 (Zero)

Está correto, nova linha em Orders foi deletada do DataTable.

Todos estes passos podem ser substituidos por um único serviço do nosso COM. É o método Update(...), que recebe como parâmetros o nome da tabela e o texto do select.

QtdeRegistrosAfetados = oNet.Update("Customers".oNet.Clientes)

Aparentemente quando acionamos o método Update() o ADO.NET emite automaticamente o método AcceptChanges() para todos os DataRows da tabela que sofreram modificação.

Código Fonte da classe Northwind (Versão C#)

(Veja a cópia em anexo no arquivo texto NorthWind.cs)

Nota : Este ensaio foi testado no Windows 2000 Pro.

Montagem da DLL

Siga as seguintes instruções:

Entre no command console do DOS (Prompt de comando Dos, não saia do Windows) E ative o programa Bloco de Notas para editar o código fonte que você copiou deste ensaio. O trabalho em conjunto deste com o Prompt de Comando do DOS facilita a vida.

a)- Inicialmente você deverá criar um <strong name>. Como o VFP vai necessitar de uma TLB para interpretar as interfaces do COM, temos de usar a identificação do COM tradicional (Unmaneged COM). Isto irá gerar uma identificação única para o nosso COM. Isto é feito uma única vez para cada COM. Seu uso pode valer para toda a Empresa. Tecle:

sn -k ciasn.snk

abreviação de <strong name> que é um utilitário do .NET Framework para gerar esta identificação única que será colocada em um arquivo especial chamado .

b)- Compile o seu programa em C# e instale a DLL resultante no global cache:

csc /t:library NorthWind.cs
gacutil /i NorthWind.dll

Observe que na compilação estamos usando a Dll produzida no item anterior. O parâmetro /t:library, agora diz ao compilador para gerar uma dll do tipo executável, ou seja as informações do metadata agora serão completas.

O utilitário , abreviação de , tem uma série de funções, por exemplo:

gacutil /l

irá listar o cache da sua máquina. Lá você poderá ver as informações da sua DLL..

c)- A DLL resultante ainda é um COM gerenciado (Managed Code). É necessário registra-la:

regasm northwind.dll

Se o VFP fosse gerenciado pelo .NET Framework esta etapa seria desnecessária. Eventualmente, você pode querer gerar uma TLB da sua dll. Isto pode ser conseguido pelo uso de um outro utilitário:

tlbexp NorthWind.dll

d)- Agora a DLL pode ser usada no formato:

ox=CREATEOBJECT("Namespace.classname")
ox.Metodo()


Marco Antonio Mazzarino, desenvolvedor de sistemas aplicativos e DBA, trabalha com C++ e VFP desde 1986 é fundador da dbRoll Sistemas e Administração de Dados, sediada em São Paulo, Brasil. Você pode contactá-lo por e-mail dbrollsistemas@aol.com.

topo


   Última Atualização: 26 de Julho de 2002
© 2003 Microsoft Corporation. Todos os direitos reservados. Nota Legal.