|
|
 |
| Tecnologias :: Visual FoxPro |
 |
|
| |
|
| 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
|
|
 |