Manuais
PHC GO Cursores
 

Introdução

Este artigo pretende ajudar na transição da framework de PHC CS para a framework de addons de PHC GO, nomeadamente trabalhar com valores que estão armazenados na base de dados.

Existem vários artigos da série Usa PHC CS com o intuito de facilitar a transição da programação da framework PHC CS em Visual FoxPro para a framework PHC GO em .NET Framework, utilizando Visual Basic.

Recomenda-se a leitura dos artigos pela ordem indicada, de forma a apresentar as diversas tecnologias e técnicas de programação em sequência crescente de complexidade e funcionalidade.


Cursores

No PHC CS, os cursores são representações em memória de registos já existentes na base de dados, ao executar uma instrução na base de dados, é criada uma área de trabalho que irá conter esses registos.

No seguinte exemplo, é definido uma expressão em Transact-SQL com um filtro para retornar os álbuns cuja data de lançamento seja superior ou igual a 1 de janeiro de 2023

Em seguida, utilizamos a função u_sqlexec para executar essa expressão na base de dados, especificando uma nova área de trabalho chamada newAlbums, que conterá os álbuns de música que obedecem ao filtro indicado.

Por fim utilizamos um bloco Scan/EndScan para mover o ponteiro interno de posicionamento da área de trabalho e processamos os álbuns sequencialmente.

* Código PHC CS  Visual FoxPro

local cTsql

m.cTsql = "Select * from u_album (nolock) where u_album.releaseDate >= '20230101'"

u_sqlexec(m.cTsql,"newAlbums")

Select newAlbums
goto top

if eof()
mensagem("Albums not found.")
return .f.
endif

Scan
Do Case


EndCase
EndScan

* it will return empty because Scan/EndScan left the cursor at EOF()
Return newAlbums.nome

No PHC GO, o termo cursor foi substituído por entidade. A representação de um registo ou coleção de registos é feito por uma variável e não uma área de trabalho, ou seja, são existe necessidade de posicionamento externo.

No seguinte exemplo, é definido uma expressão de filtro para retornar os álbuns cuja data de lançamento seja superior ou igual a 1 de janeiro de 2023.

Em seguida, utilizamos a função SDK.Query.GetEntityData para executar essa expressão na base de dados, especificando que a variável newAlbums, irá conterá os álbuns de música que obedecem ao filtro indicado.

Por fim utilizamos um bloco ForEach para percorrer todos os álbuns contidos na variável que agora representa uma coleção de registos, e processamos os álbuns sequencialmente.

' Código PHC GO – VB.NET

Dim newAlbums as New List(Of u0000_AlbumVO)

Dim myFilter as New FilterItem("u0000_album.releaseDate", Comparison.GreaterOrEqual,
"20230101".ToDate())

newAlbums = SDK.Query.GetEntityData(Of u0000_AlbumVO)(myFilter)

If newAlbums.IsVoid() Then
Return New MsgInfo("Albums not found.")
End If

For Each albumItem In newAlbums
Select Case

End Select
Next

Posicionamento

No PHC CS, é necessário mover o ponteiro de posicionamento interno da área de trabalho, utilizando, por exemplo, uma instrução Skip ou um bloco Scan/EndScan, antes de aceder aos valores do registo nessa posição.

No PHC GO, essa noção de posicionamento não existe. Em vez de uma instrução como Skip, basta indicar o valor da posição que queremos aceder ou usar instruções de bloco como ForEach.

Posicionamento de Inicio


* Código PHC CS  Visual FoxPro

Select newAlbums
goto top

mensagem("The title of the first album is: " + newAlbums.nome)

' Código PHC GO – VB.NET

' option 1
Return New MsgInfo("The title of the first album is: " + newAlbums(0).nome)

' option 2
Return New MsgInfo("The title of the first album is: " + newAlbums.First().nome)


Posicionamento de Fim


* Código PHC CS  Visual FoxPro

Select newAlbums
goto bottom

mensagem("The title of the last album is: " + newAlbums.nome)

' Código PHC GO – VB.NET

' option 1
Return New MsgInfo("The title of the last album is: " + newAlbums(newAlbums.count-1).nome)

' option 2
Return New MsgInfo("The title of the last album is: " + newAlbums.Last().nome)


Percorrer a lista


* Código PHC CS  Visual FoxPro

Select newAlbums
Scan
Do Case
Case newAlbums.country = "pt_PT"
newAlbums.obs = "Musicas Portuguesas"

Case newAlbums.country = "pt_BR"
newAlbums.obs = "Musicas Brasileiras"

Otherwise
newAlbums.obs = "Musicas do Mundo"

EndCase
EndScan

* it will return empty because Scan/EndScan left the cursor at EOF()
Return newAlbums.nome

' Código PHC GO – VB.NET

' option 1
For Each albumItem In newAlbums
Select Case albumItem.country
Case "pt_PT"
albumItem.obs = "Musicas Portuguesas"

Case "pt_BR"
albumItem.obs = "Musicas Brasileiras"

Case Else
albumItem.obs = "Musicas do Mundo"
End Select
Next

' option 2
For index as Integer = 0 To newAlbums.count()-1
Dim albumItem as u0000_AlbumVO = newAlbums(index)
Next

' option 3
For Each albumItem In (From pk In newAlbums Where pk.country="pt_PT")
albumItem.obs = "Musicas Portuguesas"
Next

' it will return not empty because ForEach/Next doesn't change the list position
Return newAlbums.nome


Só um registo

Outra diferença no PHC GO é a possibilidade de utilizar variáveis de coleção ou variáveis associadas a uma posição específica dentro da coleção. Isto pode ser feito como no exemplo anterior, onde a variável albumItem é criada em cada ciclo do ForEach, ou aplicando diretamente um comando ao retorno da função utilizada.

' Código PHC GO – VB.NET

Dim myFilter as New FilterItem("u0000_album.releaseDate", Comparison.GreaterOrEqual,
"20230101".ToDate())

Dim albumItem As u0000_AlbumVO
albumItem = SDK.Query.GetEntityData(Of u0000_AlbumVO)(myFilter).FirstOrDefault()
If albumItem Is Nothing Then
Return New MsgInfo("Albums not found.")
End If

Return New MsgInfo("The title of the first album is: " + albumItem.nome)


Verificação de vazio

Nos exemplos anteriores, apresentámos duas técnicas para verificar se a variável que recebe os dados da base de dados está vazia — ou seja, se não existem registos na tabela ou se os dados não cumprem a expressão de filtro definida.

A primeira técnica aplica-se a coleções ou a tipos de dados básicos do .NET, como textos, datas, valores lógicos e números (inteiros ou decimais). Esta verificação é realizada utilizando a função .IsVoid()

' Código PHC GO – VB.NET

Dim myText As String = ""
If myText.IsVoid() Then
' The text is empty
End If

Dim myNumber As Integer
If myNumber.IsVoid() Then
' The number is empty
End If

Dim myDate As Date
If myDate.IsVoid() Then
' The date is empty
End If

Dim myBool As Boolean
If myBool.IsVoid() Then
' The boolean is empty
End If

Dim myDecimal As Decimal
If myDecimal.IsVoid() Then
' The decimal is empty
End If

Dim myCollection As New List(Of u0000_AlbumVO)
If myCollection.IsVoid() Then
' The collection is empty
End If

Em código de frontend não existe a extensão IsVoid() por isso temos que usar o operador de igualdade === ou o operador de diferente !== ou simplesmente ! aplicado á variável.

// Código PHC GO – Typescrip

let myText: string = '';
if (myText === '') {
// The text is empty
}
if (myText !== '') {
// The text is not empty
}

let myNumber: number = 0;
if (myNumber === 0) {
// The integer ou decimal is zero (empty)
}

let emptyDate: Date = new Date('1900-01-01T00:00:00Z')
let myDate: Date = new Date();
if (myDate <= emptyDate) {
// The calendar date is empty
}
if (myDate >= emptyDate) {
// The calendar date is not empty
}

let myBool: boolean = false;
if (!myBool) {
// The boolean is false
}
if (myBool) {
// The boolean is true
}
if (myBool === true) {
// The boolean is true
}
const myCollection: u0000_AlbumVO[] = [];
if (myCollection.length === 0) {
// The collection is empty
}

A segunda técnica aplica-se a variáveis que contêm um objeto e não uma coleção de objetos, nesta situação usamos o próprio comando do .net Is Nothing ou IsNot Nothing

' Código PHC GO – VB.NET

Dim albumItem As u0000_AlbumVO
albumItem = SDK.Query.GetEntityData(Of u0000_AlbumVO)(myFilter).FirstOrDefault()
If albumItem Is Nothing Then
Return New MsgInfo("Albums not found.")
End If

Em código de frontend o valor de Is Nothing passa a ser === null e IsNot Nothing passa a ser !== null

// Código PHC GO – Typescrip

let myText: string;
if (myText === null) {
// The text is nothing
}

let myNumber: number;
if (myNumber === null) {
// The integer ou decimal is nothing
}

let myDate: Date;
if (myDate === null) {
// The calendar date is nothing
}

let myBool: boolean;
if (myBool === null) {
// The boolean is nothing
}

const myCollection: u0000_AlbumVO[];
if (myCollection.length === 0) {
// The collection is nothing
}

const myRecord: u0000_AlbumVO;
if (myRecord === null) {
// The object is nothing
}

const myObject: any;
if (myObject === null) {
// The object is nothing
}


Mensagens

No código PHC CS, quando queremos notificar o utilizador sobre uma determinada situação, utilizamos a função mensagem para exibir essa notificação no ecrã do dispositivo onde a aplicação está a ser executada.

' Código PHC CS – Visual FoxPro

Select newAlbums
goto top

if eof()
mensagem("Albums not found.")
return .f.
endif


A função mensagem é exibida imediatamente no ecrã do utilizador. Apenas depois de o utilizador a fechar, ou de esta desaparecer após alguns segundos, a aplicação continua o processamento na instrução return .f.

No PHC GO, é importante compreender que a aplicação é composta por um ambiente de frontend — a parte visual da aplicação, que funciona num navegador no computador do utilizador — e um back-end, onde residem a lógica de negócio e o armazenamento de dados, operando na nuvem.

Como a maior parte do código dos add-ons é executada no back-end, não é possível utilizar a instrução mensagem para que esta seja imediatamente exibida no navegador (front-end) e, só depois de fechada, retornar ao back-end para executar a instrução return .f.

Para contornar esta limitação, o PHC GO adota uma técnica que consiste em adicionar a mensagem a uma lista, executar as linhas de código necessárias e, apenas no final do processo, retornar essa lista para que as mensagens sejam exibidas no navegador (front-end).

' Código PHC GO – VB.NET

Public Function CheckNumber(myNumber as Integer) As List(Of MessageVO)
Dim listMsg as New List(Of MessageVO)

If myNumber.IsVoid() Then
listMsg.Add(New MsgError("The number is 0."))
End If

If myNumber < 1 Then
listMsg.Add(New MsgError("The number is less than 1."))
End If

Return listMsg
End Function


No exemplo acima, se o número enviado for igual a zero, o utilizador irá ver duas mensagens, podemos também optar por retornar uma só mensagem em vez de uma lista de mensagens.

' Código PHC GO – VB.NET

Public Function CheckNumber(myNumber as Integer) As MessageVO

If myNumber.IsVoid() Then
Return New MsgError("The number is 0.")
End If

If myNumber < 1 Then
Return New MsgError("The number is less than 1.")
End If

Return Nothing
End Function


No PHC GO, existem várias funções para criar mensagens: MsgInfo() para mensagens informativas, que aparecem em cinzento; MsgError() para mensagens de erro, exibidas em vermelho; e MsgWarning() para mensagens de aviso, apresentadas em amarelo.

No caso da função MsgWarning(), existe uma situação especial no momento AoGravar, em que, em vez de exibir o texto como uma mensagem de aviso, é apresentada uma pergunta ao utilizador com o texto fornecido, solicitando uma resposta de Sim ou Não. Esta funcionalidade será abordada em maior detalhe no manual: Usa PHC CS – Regras de Negócio.

Nome de Tabelas


' Código PHC CS – Visual FoxPro

local cTsql
m.cTsql = "Select * from u_album (nolock) where u_album.releaseDate >= '20230101'"
u_sqlexec(m.cTsql,"newAlbums")


Na expressão Transact-SQL, indicamos que queremos retornar registos de uma tabela existente na base de dados com o nome u_album.

O prefixo u_ indica que se trata de uma tabela de utilizador. Ao customizar uma tabela na opção Supervisor - Framework PHC - Tabelas do Utilizador, a aplicação adiciona automaticamente o prefixo ao início do nome da tabela.

Isto assegura que o nome da tabela não entre em conflito com outras tabelas do sistema, ou seja, com tabelas nativas da aplicação que possam ter o mesmo nome.

Em PHC GO as tabelas de utilizador também usam o prefixo u_ acrescentado de um número. Isto porque as tabelas de utilizador são criadas por um determinado add-on, desenvolvido por um parceiro da PHC, e cada parceiro possui um ID único, nos exemplos apresentados vamos utilizar o número 0000.

' Código PHC GO – VB.NET

Dim newAlbums As New List(Of u0000_AlbumVO)

Dim myFilter as New FilterItem("u0000_album.releaseDate", Comparison.GreaterOrEqual,
"20230101".ToDate())

newAlbums = SDK.Query.GetEntityData(Of u0000_AlbumVO)(myFilter)


No entanto, em memória, ou seja, no código da aplicação, essa tabela adquire o sufixo VO no final do nome, resultando em u0000_AlbumVO. Esse sufixo VO, que significa value object.

Tipificação

No PHC GO, existe uma classe específica para cada tabela da base de dados. Assim, quando precisamos retornar registos de uma tabela, utilizamos a classe correspondente para representar o cursor em memória, um conceito conhecido como tipificação.

A tabela de álbuns existente na base de dados, u0000_album, é representada por uma classe específica na aplicação, u0000_AlbumVO, que faz o mapeamento entre a tabela na base de dados e os dados em memória na aplicação.

' Código PHC GO – VB.NET

Dim newAlbums As New List(Of u0000_AlbumVO)

Dim myFilter as New FilterItem("u0000_album.releaseDate", Comparison.GreaterOrEqual,
"20230101".ToDate())


O sufixo VO no final do nome é uma tipologia adotada pela PHC que significa Value Object. Este termo refere-se a uma classe ou objeto que apenas contém dados, sem regras de negócio associadas, e cuja finalidade é representar os valores da base de dados que serão utilizados nas operações de serialização e desserialização durante as comunicações entre o back-end e o front-end.

Esta classe é criada automaticamente pela aplicação quando se instala um add-on e obedece ás configurações feitas na opção GO Studio - Toolbox - Ecrãs e Entidades, em PHC GO uma tabela é definida quando se constrói o desenho do seu ecrã.

' Código PHC GO – VB.NET

Public Class u0000_albumVO

<AttLenght(25), AttFieldStamp()>
Public Property [u0000_albumstamp]() As String

<AttLenght(80),AttList(),AttLogInfo()>
Public Property [nome]() As String

<AttLenght(40),AttList(),AttLogInfo()>
Public Property [singername]() As String

End Class


Coleções em PHC CS

No PHC CS, quando uma tabela é composta, como no caso da tabela u_album, em que cada álbum pode ter várias músicas associadas, estas são armazenadas numa tabela separada, u_musica, com uma relação entre ambas as tabelas. Assim, temos duas tabelas físicas na base de dados e dois cursores no código da aplicação.

Por exemplo, o cursor u_album pode conter um registo específico, como o álbum "Cantigas de Portugal". O cursor u_musica, filtrado pela chave estrangeira u_albumstamp, exibirá apenas as músicas associadas a esse álbum específico.

* Código PHC CS  Visual FoxPro

local uniqueID,msel,musicList

m.uniqueID = "Z_20240505155423284756456"

m.msel = "Select * from u_album (nolock) where u_album.u_albumstamp='"+m.uniqueID+"'"
if not u_sqlexec(m.msel,"u_album")
mensagem("Unable to open the album.")
return .f.
endif

select u_album
goto top
if eof()
mensagem("Album not found.")
return .f.
endif

m.msel = "Select * from u_musica (nolock) where u_musica.u_albumstamp='"+m.uniqueID+"'"
if not u_sqlexec(m.msel,"u_musica")
mensagem("Unable to open the musics.")
return .f.
endif

select u_musica
goto top
if eof()
mensagem("The musics for the album " + albumItem.nome + " is empty.")
return .t.
endif

m.musicList = "The album " + albumItem.nome + " have this list of musics:"

select u_musica
goto top
scan
m.musicList = m.musicList + u_musica.nome + chr(13)
endscan

mensagem(m.musicList)

return .t.


Coleções em PHC GO

No PHC GO, a situação é semelhante ao PHC CS: existem duas tabelas físicas na base de dados, uma para os álbuns e outra para as músicas.

A grande diferença é que a coleção de músicas é representada dentro da estrutura da tabela álbum. Ou seja, cada registo da entidade u0000_AlbumVO contém uma propriedade do tipo coleção com o nome u0000_musica, que armazena todas as músicas de um álbum específico.

' Código PHC GO – VB.NET

Public Class u0000_albumVO

<AttLenght(25), AttFieldStamp()>
Public Property [u0000_albumstamp]() As String

<AttCollection(GetType(u0000_musicaVO))>
Public Property u0000_musica As List(Of u0000_musicaVO)

End Class


A tabela de músicas existente na base de dados, u0000_musica, é também representada por uma classe específica na aplicação, u0000_MusicaVO, que faz o mapeamento entre a tabela na base de dados e os dados em memória na aplicação.

' Código PHC GO – VB.NET

Public Class u0000_musicaVO

<AttLenght(25,0),AttFieldStamp()>
Public Property [u0000_musicastamp]() As String

<AttLenght(25,0),AttForeignKey(GetType(u0000_albumVO)),AttIndex()>
Public Property [u0000_albumstamp]() As String

<AttLenght(80,0)>
Public Property [nome]() As String

End Class


Ao obter um álbum da base de dados, a aplicação traz automaticamente as músicas associadas a esse álbum, eliminando a necessidade de código adicional para essa função, como era necessário anteriormente no PHC CS.

É importante destacar que a propriedade de coleção u0000_musica utiliza o tipo Lazy Loading. Isso significa que, após solicitar um determinado álbum, as músicas não são carregadas imediatamente. Somente quando se faz referência à propriedade u0000_música, as músicas são então solicitadas e carregadas da base de dados.

' Código PHC GO – VB.NET

Dim uniqueID As String = "Z_20240505155423284756456"

' in this instruction we only obtain the album data without the songs.
Dim albumItem As u0000_AlbumVO = SDK.Query.GetEntityByStamp(Of u0000_AlbumVO)(uniqueID)
If albumItem Is Nothing Then
Return New MsgError("Album not found.")
End If

' the songs are returned from the database in this instruction.
If albumItem.u0000_musica.IsVoid() Then
Return New MsgInfo(String.Format("No musics in album {0}.", albumItem.nome))
End If

Dim musicList As New Text.StringBuilder()
musicList.AppendLine(String.Format("Musics of album {0}", albumItem.nome))

For Each musicaItem As u0000_MusicaVO In albumItem.u0000_musica
musicList.AppendLine(musicaItem.Nome)
Next

Return New MsgInfo(musicList.ToString())


Views

No PHC CS, ao criar, modificar ou eliminar registos na base de dados, em vez de utilizarmos um cursor obtido, por exemplo, através da função u_sqlexec, recorremos a uma técnica chamada view.

Ao utilizar a função dbfusetabusrnome, usada para tabelas de utilizador, o cursor criado possui propriedades ativadas que simplificam o processo de persistência das alterações na base de dados.

* Código PHC CS  Visual FoxPro

Do dbfusetabusrnome with "album"
if not Used("u_album")
mensagem("Unable to open the album.")
return .f.
endif


No PHC GO, como as entidades estão tipificadas, cada tabela da base de dados é sempre representada por uma classe específica na aplicação. A obtenção dos valores é realizada da mesma forma explicada anteriormente nos exemplos de cursores.

' Código PHC GO – VB.NET

' get some record
Dim uniqueID As String = "Z_20240505155423284756456"

Dim albumItem As u0000_AlbumVO = SDK.Query.GetEntityByStamp(Of u0000_AlbumVO)(uniqueID)
If albumItem Is Nothing Then
Return New MsgError("Album not found.")
End If


Views – Funcionalidades

No PHC GO, as entidades herdam de uma classe de negócio base que contém diversas funcionalidades comuns a todas as entidades do sistema, sejam estas entidades principais, secundárias ou específicas de utilizador.

  • IsEmptyInstance() as Boolean

    Quando se cria manualmente uma nova instância de uma entidade, o valor desta propriedade assume True. No momento em que se atribui um valor a qualquer campo dessa entidade, a propriedade muda automaticamente para False.

  • Operation as Integer

    Indica a operação a ser efetuada na base de dados para um registo, pode assumir um dos seguintes valores:

    0 = None, nada será efetuado na base de dados.
    1 = Inserted, o registo vai ser criado na base de dados.
    2 = Update, o registo já existe na base de dados e vai ser modificado.
    3 = Delete, o registo existe na base de dados e vai ser removido.

  • IsChanged() as Boolean

    As entidades conseguem rastrear o valor original e o valor atual de cada campo. Esta função retorna True caso algum campo tenha sido alterado, ou False caso contrário.

    Quando a entidade contém coleções de outras entidades secundárias, a verificação é feita em todos os registos e em todos os campos dessas coleções. Por exemplo, se a fatura 53 contiver 20 linhas de artigos e apenas o campo quantidade da linha 19 for alterado, a função retornará True quando aplicada à fatura, e não apenas à linha 19.

    Devido a este comportamento, é importante considerar questões de desempenho ao utilizar esta função em entidades compostas que incluam um grande número de registos nas suas coleções.

  • IsFieldChanged(fieldName As String) as Boolean
  • IsAnyFieldChanged(ParamArray fieldsName() As String) as Boolean
  • IsAnyFieldChanged(fieldsName As IEnumerable(Of String)) as Boolean
  • IsOnlyAnyThisFieldsChanged(ParamArray fieldsName() As String) as Boolean

    Estas funções permitem verificar se um campo específico ou um conjunto de campos foi modificado.

  • OldValue(Of T)(fieldName As String) as T
  • OldValue(fieldName As String) as Object

    Estas funções permitem verificar se um campo específico ou um conjunto de campos foi alterado.

  • ousrinis As String
  • ousrdata As DateTime
  • ousrhora As String

    Este são campos de sistema, preenchidos automaticamente, que indicam qual foi o utilizador que criou o registo, bem como a data e hora dessa operação.

  • usrinis As String
  • usrdata As DateTime
  • usrhora As String

    São campos de sistema, preenchidos automaticamente, que indicam qual foi o último utilizador que alterou o registo, bem como a data e hora dessa operação.


Views – Criar registos

No PHC CS utilizamos a instrução append blank associada a uma view, quando queremos criar um novo registo na base de dados.

* Código PHC CS  Visual FoxPro

Do dbfusetabusrnome with "album"
if not Used("u_album")
mensagem("Unable to open the album.")
return .f.
endif

* create
select u_album
append blank

* fill the values
u_album.u_albumstamp = "Z_20240505155423284756456"
u_album.nome = "The Best Portuguese Pimba"
u_album.percious = .t.

* persist changes to the database.
if not u_tabupdate(.t., .t., "u_album")
mensagem("Unable to create the album: " + u_album.nome)
return .f.
endif

return .t.


No PHC GO, existe uma classe gestora da entidade, que contém todas as regras de negócio e ações disponíveis para essa entidade. Uma dessas ações é a gravação Save, que deve ser utilizada para executar alterações de valores na base de dados.

' Código PHC GO – VB.NET

Dim listMsg as New List(Of MessageVO)

' entity business controller
Dim albumBiz As SDKBiz = SDK.Business.CreateBiz("u0000_Album")
If albumBiz Is Nothing Then
listMsg.Add(New MsgError("The album business is not available."))
Return listMsg
End If

' create
Dim albumItem As u0000_AlbumVO = u0000_AlbumVO.GetNewInstance(Of u0000_AlbumVO)()

' Note:
' albumItem.IsEmptyInstance = True
' albumItem.Operation = OperationEnum.Inserted = 1
' albumItem.u0000_albumstamp = Some unique value, ex: Z_20240505155423284756456

' fill the values
albumItem.nome = "The Best Portuguese Pimba"
albumItem.percious = .t.

' Note:
' albumItem.IsEmptyInstance = False
' albumItem.IsFieldChanged("percious") = True
' albumItem.OldValue(Of Boolean)("percious") = False

' create record on the database.
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


Ao executar a ação Save, são aplicadas as regras de sistema, assim como quaisquer regras adicionadas através dos add-ons instalados na aplicação. Adicionalmente, todos os triggers associados são executados, garantindo a coerência dos dados.

No PHC GO, não existem triggers na base de dados; por isso a criação, alteração ou apagar de registos da base de dados deve passar sempre pela classe gestora da entidade. Qualquer atualização direta irá comprometer a consistência dos dados.

Views – Alterar registos

No PHC CS, para alterar um registo, podemos usar a função c_requery para retornar os valores da base de dados, proceder à alteração dos valores desejados e, em seguida, gravar as modificações com u_tabupdate

* Código PHC CS  Visual FoxPro

Do dbfusetabusrnome with "album"
if not Used("u_album")
mensagem("Unable to open the album.")
return .f.
endif

* get some record
m.v_u_albumstamp = "Z_20240505155423284756456"
c_requery("u_album")

select u_album
goto top
if eof()
mensagem("Album not found.")
return .f.
endif

* changing something in the album, for example, now becomes my precious album.
u_album.percious = .t.

* persist changes to the database.
if not u_tabupdate(.t., .t., "u_album")
mensagem("Unable to update the album: " + u_album.nome)
return .f.
endif

return .t.


No PHC GO, obtemos o registo de forma habitual, alteramos os seus valores e gravamos as modificações através da respetiva classe de negócio.

' Código PHC GO – VB.NET

Dim listMsg as New List(Of MessageVO)

' get some record
Dim uniqueID As String = "Z_20240505155423284756456"

Dim albumItem As u0000_AlbumVO = SDK.Query.GetEntityByStamp(Of u0000_AlbumVO)(uniqueID)
If albumItem Is Nothing Then
Return New MsgError("Album not found.")
End If

' Note:
' albumItem.IsEmptyInstance = False
' albumItem.Operation = OperationEnum.None = 0
' albumItem.u0000_albumstamp = Z_20240505155423284756456

' entity business controller
Dim albumBiz As SDKBiz = SDK.Business.CreateBiz("u0000_Album")
If albumBiz Is Nothing Then
listMsg.Add(New MsgError("The album business is not available."))
Return listMsg
End If

' changing something in the album, for example, now becomes my precious album.
albumItem.percious = .f.

' Note:
' albumItem.Operation = OperationEnum.Update = 2
' albumItem.IsFieldChanged("percious") = True
' albumItem.OldValue(Of Boolean)("percious") = True

' persist changes to the database.
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


Views – Apagar registos

No PHC CS utilizamos a instrução delete associada a uma view, quando queremos apagar um registo na base de dados.

* Código PHC CS  Visual FoxPro

Do dbfusetabusrnome with "album"
if not Used("u_album")
mensagem("Unable to open the album.")
return .f.
endif

* get some record
m.v_u_albumstamp = "Z_20240505155423284756456"
c_requery("u_album")

select u_album
goto top
if eof()
mensagem("Album not found.")
return .f.
endif

* mark as deleted
delete

* persist changes to the database.
if not u_tabupdate(.t., .t., "u_album")
mensagem("Unable to delete the album: " + u_album.nome)
return .f.
endif

return .t.


No PHC GO, obtemos o registo de forma habitual, modificamos o valor da propriedade operation e apagamos o registo através da respetiva classe de negócio.

' Código PHC GO – VB.NET

Dim listMsg as New List(Of MessageVO)

' get some record
Dim uniqueID As String = "Z_20240505155423284756456"

Dim albumItem As u0000_AlbumVO = SDK.Query.GetEntityByStamp(Of u0000_AlbumVO)(uniqueID)
If albumItem Is Nothing Then
Return New MsgError("Album not found.")
End If

' entity business controller
Dim albumBiz As SDKBiz = SDK.Business.CreateBiz("u0000_Album")
If albumBiz Is Nothing Then
listMsg.Add(New MsgError("The album business is not available."))
Return listMsg
End If

' mark as deleted
albumItem.Operation = OperationEnum.Delete

' delete the record from the database
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


Uma nota sobre a operação de apagar: se a entidade principal contiver coleções, ao marcar a entidade principal como Delete, todas as coleções são automaticamente marcadas da mesma forma. Assim, na base de dados, toda a estrutura é removida, evitando que registos fiquem perdidos.

Views – Criar, Alterar e Apagar registos de Coleções

No PHC GO, ao aplicar operações nas coleções de uma entidade principal, é necessário primeiro obter a entidade principal e, em seguida, realizar a operação desejada na coleção.

A gravação deve sempre ser feita através da classe gestora da entidade, sendo que apenas as entidades principais dispõem desse tipo de classes.

Por exemplo, o método Save da classe gestora da entidade álbuns aceita apenas objetos do tipo u0000_AlbumVO, e não possui uma assinatura que permita receber uma coleção de objetos u0000_MusicaVO.

Nos exemplos a seguir, vamos realizar estas três operações na coleção de músicas de um álbum específico, utilizando a respetiva classe gestora.

' Código PHC GO – VB.NET

Dim listMsg as New List(Of MessageVO)

' entity business controller
Dim albumBiz As SDKBiz = SDK.Business.CreateBiz("u0000_Album")
If albumBiz Is Nothing Then
listMsg.Add(New MsgError("The album business is not available."))
Return listMsg
End If


Criar um álbum com uma música.


' Código PHC GO – VB.NET

Dim albumItem As u0000_AlbumVO = u0000_AlbumVO.GetNewInstance(Of u0000_AlbumVO)()

' fill the values in the album
albumItem.nome = "The Best Portuguese Pimba"
albumItem.percious = .t.

' add a song
Dim musicItem as u0000_MusicaVO = albumItem.AddChild(Of u0000_MusicaVO)

' fill the values in the song
musicItem.nome = "O que elas querem é pimba"

' create the album with a song on the database
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


Criar um álbum com uma música para quem adora de escrever código.


' Código PHC GO – VB.NET

Dim albumItem As u0000_AlbumVO = u0000_AlbumVO.GetNewInstance(Of u0000_AlbumVO)()

' fill the values in the album
albumItem.nome = "The Best Portuguese Pimba"
albumItem.percious = .t.

' add a song
Dim musicItem as u0000_MusicaVO = u0000_MusicaVO.GetNewInstance(Of u0000_MusicaVO)()

' relation
musicItem.u0000_albumstamp = albumItem.u0000_albumstamp
musicItem.parentVO = albumItem

' fill the values in the song
musicItem.nome = "O que elas querem é pimba"

' add to collection
albumItem.u0000_musica.add(musicItem)

' create the album with a song on the database.
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


Alterar uma música e adicionar uma nova.


' Código PHC GO – VB.NET

' get some record
Dim uniqueID As String = "Z_20240505155423284756456"

Dim albumItem As u0000_AlbumVO = SDK.Query.GetEntityByStamp(Of u0000_AlbumVO)(uniqueID)
If albumItem Is Nothing Then
Return New MsgError("Album not found.")
End If

' the song
Dim musicItem as u0000_MusicaVO = Nothing

' find the song, option 1
musicItem = (From pk in albumItem.u0000_musica
Where pk.nome="O que elas querem é pimba").FirstOrDefault

' find the song, option 2
If albumItem.u0000_musica.any
musicItem = albumItem.u0000_musica(0)
End If

' find the song, option 3
If albumItem.u0000_musica.count >= 1
musicItem = albumItem.u0000_musica(0)
End If

' changing the song name
If musicItem IsNot Nothing Then
musicItem.nome = "O que elas querem ás vezes é pimba"
End If

' add a new song
'
' musicItem is a is a pointer, changing it in the next instruction
' does not affect the first song at all
musicItem = albumItem.AddChild(Of u0000_MusicaVO)

' fill the values in the new song
musicItem.nome = "O bacalhau quer …"

' persist changes to the database.
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


Apagar uma música


' Código PHC GO – VB.NET

' get some record
Dim uniqueID As String = "Z_20240505155423284756456"

Dim albumItem As u0000_AlbumVO = SDK.Query.GetEntityByStamp(Of u0000_AlbumVO)(uniqueID)
If albumItem Is Nothing Then
Return New MsgError("Album not found.")
End If

' find the song
Dim musicItem as u0000_MusicaVO = (From pk in albumItem.u0000_musica
Where pk.nome="O bacalhau quer …").FirstOrDefault

' mark as deleted.
If musicItem IsNot Nothing Then
musicItem.Operation = OperationEnum.Delete
End If

' persist changes to the database.
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


Apagar todas as músicas


' Código PHC GO – VB.NET

' get some record
Dim uniqueID As String = "Z_20240505155423284756456"

Dim albumItem As u0000_AlbumVO = SDK.Query.GetEntityByStamp(Of u0000_AlbumVO)(uniqueID)
If albumItem Is Nothing Then
Return New MsgError("Album not found.")
End If

' delete all
albumItem.u0000_musica.Clear()

' persist changes to the database.
listMsg.AddRange(albumBiz.Save(newAlbums))

Return listMsg


SDK – QUERY

https://helpcenter.phcgo.net/pt/sug/ptxview.aspx?stamp=d5183bc7eb7f7e%3a%3agd1562

Nos manuais disponíveis no Developers Help Center, há um tópico dedicado às operações de retorno de valores da base de dados. As funções desta categoria, podem receber desde algo simples, como o valor de stamp de um determinado registo, até estruturas mais avançadas, como filtros e definições de pesquisa (QueryVO).

Neste contexto, iremos abordar essas estruturas auxiliares, algumas das quais já foram incluídas nos exemplos apresentados nos manuais desta série Usa PHC CS.

Uma nota importante: todas as funções desta categoria estão agrupadas em dois tipos de funcionalidades:

  • Funções que obedecem aos filtros de sistema e utilizador implementados para a entidade em questão.
  • Funções que ignoram esses filtros, considerando apenas os filtros fornecidos no momento da chamada da função. Caso nenhum filtro seja passado, a função realiza uma pesquisa livre na base de dados


SDK – QUERY – Com Filtros Automáticos

Estas funções são amplamente utilizadas na rotina comum de acesso à base de dados. Por exemplo, se um Add-On implementou um filtro que define que, na entidade de documentos de faturação, cada vendedor só pode visualizar as suas próprias faturas.

Espera-se que, ao realizar queries para essa entidade, seja a partir do frontend ou do backend, esse filtro seja automaticamente aplicado, sem a necessidade de o definir repetidamente em cada consulta.

Get Values


  • GetValues(Of T) (entityName As String, field As String) as List(Of T)
  • GetValues(Of T) (entityName As String, field As String, filter As FilterItem) as List(Of T)
  • GetValues(Of T) (entityName As String, field As String, filter As FilterItems) as List(Of T)

O exemplo seguinte mostra como retornar todos os números internos (ndoc) das séries de documentos de faturação (td) que estejam configuradas para lançar em conta corrente (lancacc).

Além do filtro aqui especificado, é automaticamente incluído os filtros de acesso por utilizador que possam estar definidos na aplicação.

' Código PHC GO – VB.NET

Dim allConfigNumbers as New List(Of Decimal)
allConfigNumbers = SDK.Query.GetValues(Of Decimal)("td", "ndoc",
New FilterItem("lancacc", Comparison.Equal, True))


Neste exemplo como queremos adicionar mais um filtro para considerar apenas as séries configuradas para controlar plafond de crédito (nocredit), optamos por definir primeiro uma coleção de filtros e enviar essa coleção para a função.

' Código PHC GO – VB.NET

Dim myFilter As FilterItems = New FilterItems()
myFilter.Add(New FilterItem("lancacc", Comparison.Equal, True))
myFilter.Add(New FilterItem("nocredit", Comparison.Equal, True))

Dim allConfigNumbers as New List(Of Decimal)
allConfigNumbers = SDK.Query.GetValues(Of Decimal)("td", "ndoc", myFilter)


Get Entity



  • GetEntityData(Of T) () As List(Of T)
  • GetEntityData(Of T) (filter As FilterItem) As List(Of T)
  • GetEntityData(Of T) (filter As FilterItems) As List(Of T)

    Importante: Os registos obtidos por estas funções, podem ser usados na função SDKBIZ.Save, para gravar possíveis alterações efetuadas após a sua obtenção da base de dados.

    No exemplo seguinte retornamos da base de dados um registo da entidade de clientes usando um filtro pelo nome do cliente (nome).

    ' Código PHC GO – VB.NET

    Dim client as ClVO = SDK.Query.GetEntityData(Of ClVO)(New FilterItem("nome",
    Comparison.Equal, "'Bernardo Santiago'")).FirstOrDefault()

  • GetEntityData(Of T) (queryItem As QueryVO) As List(Of T)

    Importante: Os registos obtidos por esta função, só podem ser usados na função SDKBIZ.Save, para gravar possíveis alterações efetuadas após a sua obtenção da base de dados, caso a propriedade SelectItems esteja vazia.

  • GetEntityData(queryItem As QueryVO) as IList

    Importante: Os registos retornados por esta função não podem ser utilizados na função SDKBIZ.Save. Tentar fazê-lo resultará em erro de compilação no Add-On.

  • GetEntityByStamp(Of T)(stamp As String) As T

    Importante: Os registos obtidos por esta função, podem ser usados na função SDKBIZ.Save, para gravar possíveis alterações efetuadas após a sua obtenção da base de dados.

  • GetEntityDynamic(queryItem as QueryVO) as IList

    Os registos retornados por esta função não são tipificados, por isso, o retorno é uma lista de objetos, sendo necessário usar uma interface para definir o tipo de retorno.

    Importante: Os registos retornados por esta função não podem ser utilizados na função SDKBIZ.Save. Tentar fazê-lo resultará em erro de compilação no Add-On.

    Esta função é especialmente útil quando precisamos executar uma query na base de dados que retorna valores pertencentes a mais de uma entidade.

    No exemplo seguinte, retornamos apenas alguns campos da entidade de artigos e serviços (st), juntamente com campos da entidade de stock por armazém (sa). Para isso, definimos uma ligação entre as duas entidades.

    ' Código PHC GO – VB.NET

    ' main table
    Dim query As New QueryVO("sa")

    ' select the columns we want
    query.SelectItems.Add("sa.armazem")
    query.SelectItems.Add("sa.ref")
    query.SelectItems.Add("sa.stock")
    query.SelectItems.Add("st.design")

    ' create the join
    Dim join as New JoinEntity("st")

    ' join expression
    join.joinExp.Add(New FilterItem("st.ref",Comparison.Equal,"sa.ref"))

    ' add
    query.joinEntities.Add(join)

    ' order
    query.OrderByItems.Add(New OrderByItem("sa.armazem", OrderTypes.Descending))

    ' result
    Dim result as Ilist = SDK.Query.GetEntityData(query)
    For each item as Object in result
    Dim parseTxt as String
    parseTxt = String.Format("The item: {0} has {1} units",
    item.design,item.stock)
    Next



SDK – QUERY – Sem Filtros Automáticos

Estas funções são mais específicas e destinam-se a operações concretas, muitas vezes relacionadas com a criação de novos registos, onde é necessário cumprir determinadas regras de negócio.

Por exemplo, no caso dos filtros automáticos mencionados anteriormente, como o filtro de vendedor, esse comportamento não é desejável em situações como a determinação do próximo número a atribuir a uma fatura em criação.

De acordo com as regras de certificação, o número deve ser sequencial e sem falhas, independentemente dos acessos configurados para os utilizadores do sistema.

Get Values



  • GetOneValue(Of T) (entityName As String, field As String) As T
  • GetOneValue(Of T) (entityName As String, field As String, filter As FilterItem) As T
  • GetOneValue(Of T)(entityName As String, field As String, filter As FilterItems) As T
  • GetOneValue(Of T)(entityName As String, field As String, aggregateType As Aggregate) As T
  • GetOneValue(Of T)(entityName As String, field As String, aggregateType As Aggregate, filter As FilterItem) As T
  • GetOneValue(Of T)(entityName As String, field As String, aggregateType As Aggregate, filter As FilterItems) As T
  • GetNumRecords (entityName As String) As Int32
  • GetNumRecords(entityName As String, filter As FilterItem) As Int32
  • GetNumRecords(entityName As String, filter As FilterItems) As Int32


Get Entity



  • GetEntityData(queryItem as String) as IList

    Os registos retornados por esta função não são tipificados, por isso, o retorno é uma lista de objetos, sendo necessário usar uma interface para definir o tipo de retorno.

    Importante: Os registos retornados por esta função não podem ser utilizados na função SDKBIZ.Save. Tentar fazê-lo resultará em erro de compilação no Add-On.

    O exemplo a seguir é semelhante ao apresentado para a função GetEntityDynamic(queryItem As QueryVO) As IList. A principal diferença é que, como esta função recebe um comando Transact-SQL em formato de texto, não há uma forma segura de injetar os filtros definidos na aplicação para a entidade em questão.

    Por esse motivo, esta função está categorizada no grupo SDK – QUERY – Sem Filtros Automáticos.

    ' Código PHC GO – VB.NET

    Dim result as IList = SDK.Query.GetEntityData("select sa.armazem, sa.ref,
    st.design, sa.stock from sa inner join st on sa.ref = st.ref order by sa.armazem")

    For each item as Object in result
    Dim parseTxt as String
    parseTxt = String.Format("The item: {0} has {1} units",
    item.design,item.stock)
    Next



Utils



  • ExistRecord(entityName As String) As Boolean
  • ExistRecord(entityName As String, filtro As FilterItem) As Boolean
  • ExistRecord(entityName As String, filtros As FilterItems) As Boolean

    No exemplo seguinte, verificamos se existe algum registo na tabela de clientes que no campo (zona) tenham o valor “Centro”.

    ' Código PHC GO – VB.NET

    Dim fromCenter as Boolean = SDK.Query.ExistRecord("cl",New FilterItem("zona",
    Comparison.Equal, "'Centro'"))



SDK – QUERY – QueryVO

Esta classe descreve um comando Transact-SQL. Permite definir as propriedades que devem ser incluídas no comando enviado para a base de dados. Após configurar as propriedades, basta chamar uma das funções da categoria SDK – Query que aceitem um QueryVO nos seus parâmetros de entrada.

' Código PHC GO – VB.NET

Class QueryVO

#Region "Properties"

Property entityName As String = ""

Property limit As Integer

Property distinct As Boolean

#End Region

#Region "Groups"

Property SelectItems As New List(Of String)

Property filterItems As New List(Of FilterItem)

Property orderByItems As New List(Of OrderByItem)

Property joinEntities As New List(Of JoinEntity)

Property groupByItems As New List(Of GroupByItem)

#End Region

#Region "Filter"

Property ndoc As Integer

ApplyCoreFilters As Boolean = True

#End Region

End Class


A propriedade ApplyCoreFilters, permite controlar a característica da função chamada respeitar ou não os filtros de sistema e de utilizador.

Importante: Caso os registos obtidos sejam destinados a alterações e posterior gravação na base de dados NUNCA adicione valores à propriedade SelectItems.

SDK – QUERY – FilterItem


' Código PHC GO – VB.NET

Class FilterItem

#Region "Factory"

New()

New(filterItem As String, comparison As Comparison, valueItem As Object)

New(filterItem As String, valueItem As Object)

New(filterItem As String, comparison As Comparison,

valueItem As Object, groupItem As FilterGroupItem)

New(filterItem As String, valueItem As Object, groupItem As FilterGroupItem)

New(filterItem As String)

New(groupItem As FilterGroupItem)

#End Region

#Region "Properties"

Property filterItem As String = ""

Property comparison As Comparison = Comparison.Equal

Property valueItem As Object

Property groupItem As FilterGroupItem = FilterGroupItem.And

#End Region

End Class


SDK – QUERY – Comparison


' Código PHC GO – VB.NET

Enum Comparison As Integer
None = -1

Equal = 0
NotEqual = 1

Greater = 2
GreaterOrEqual = 3
Less = 4
LessOrEqual = 5

StartWith = 6
NotStartWith = 7
EndWith = 8
NotEndWith = 9

Contain = 10
NotContain = 11

Exist = 12
EqualLike = 13
NotEqualLike = 14
In = 15
NotExist = 16
NotIn = 17

Blank = 18
IsNULL = 19
IsNotNULL = 20
End Enum


SDK – QUERY – FilterGroupItem


' Código PHC GO – VB.NET

Enum FilterGroupItem As Integer
None = 0

And = 1
AndNot = 2
AndBkt = 3
AndNotBkt = 4
BktAndBkt = 5
BktAndNotBkt = 6
BktAnd = 7
BktAndNot = 8

Or = 9
OrNot = 10
OrBkt = 11
OrNotBkt = 12
BktOrBkt = 13
BktOrNotBkt = 14
BktOr = 15
BktOrNot = 16

CloseBkt = 17
OpenBkt = 18
End Enum


SDK – QUERY – JoinEntity


' Código PHC GO – VB.NET

Class JoinEntity

#Region "Factory"

New()

New(tableName As String)

New(tableName As String, plainJoin As String)

New(tableName As String, joinType As JoinsType)

New(tableName As String, joinType As JoinsType, plainExp As String)

New(tableName As String, joinType As JoinsType, joinExpression As FilterItem)

#End Region

#Region "Properties"

Property tableName As String = ""

Property entityAlias As String = ""

Public Property subselect As String = ""

Property joinType As JoinsType = JoinsType.Inner

Property joinExp As New List(Of FilterItem)

Property plainJoin As String = ""

#End Region
End Class


SDK – QUERY – GroupByItem


' Código PHC GO – VB.NET

Class GroupByItem

#Region "Factory"

New()

New(groupByItem As String)

#End Region

#Region "Properties"

Property GroupByItem As String = ""

#End Region
End Class


SDK – QUERY – OrderByItem


' Código PHC GO – VB.NET

Class OrderByItem

#Region "Factory"

New()

New(orderItem As String)

New(orderItem As String, orderType As OrderTypes)

New(orderItem As String, orderType As String)

New(orderItem As String, aggregateType As Aggregate)

New(orderItem As String, orderType As OrderTypes, aggregateType As Aggregate)

New(orderItem As String, orderType As String, aggregateType As Aggregate)

#End Region

#Region "Properties"

Property OrderItem As String = ""

Property OrderType As OrderTypes = OrderTypes.Ascending

Property AggregateType As Aggregate = Aggregate.None

#End Region
End Class


SDK – QUERY – OrderTypes


' Código PHC GO – VB.NET

Enum OrderTypes As Integer
None = -1
Ascending = 0
Descending = 1
End Enum


SDK – QUERY – Aggregate


' Código PHC GO – VB.NET

Enum Aggregate As Integer
None = 0

Avg = 1
Count = 2
Min = 3
Max = 4
Sum = 5

NullMin = 6
Accumulated = 7
InvertedAccumulated = 8
End Enum