Escrito por

Sales Engineer at InterSystems
Artigo Danusa Calixto · Ago. 12, 2022 4m read

Cuidado ao combinar OO e SQL

A combinação da sintaxe de objetos com SQL é um dos recursos legais no Object Script. No entanto, em um caso, forneceu resultados estranhos. Portanto, decidi isolar esse caso e descrevê-lo aqui.

Digamos que você precisa escrever um classmethod que atualiza uma única propriedade no disco. Geralmente, eu escreveria isso usando SQL, desta forma:

ClassMethod ActivateSQL(customerId) as %Status
{
   &sql(Update Test.Customer Set Active=1 Where ID=:customerId)
   If SQLCODE'=0 {
      Set exception = ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE, $Get(%msg))
      Quit exception.AsStatus()
   } Else {
      Quit $$$OK
   }
}
 

e chamaria esse classmethod sempre que necessário no meu aplicativo.

No entanto, se o código do aplicativo tiver a instância aberta durante a chamada do classmethod e executar %Save em seguida, ele substituirá as atualizações realizadas no classmethod:

Set objCust=##class(Test.Customer).%OpenId(id)
Do objCust.ActivateSQL(id)
Set objCust.Name = "something"
Set sc = objCust.%Save()


Ao mudar a ordem das linhas, o problema estaria resolvido, mas você precisa tomar cuidado com este tipo de combinação:

Do ##class(Test.Customer).ActivateSQL(id)
Set objCust=##class(Test.Customer).%OpenId(id)
Set objCust.Name = "something"
Set sc = objCust.%Save()


Quando classmethod fosse escrito usando sintaxe de OO assim:

ClassMethod ActivateOO(customerId) as %Status
{
Set objCust = ##class(Test.Customer).%OpenId(customerId)
Set objCust.Active = 1
Quit objCust.%Save()
}

não haveria problema, já que a instância aberta no código da chamada e a instância aberta em classmethod apontariam para a mesma instância na memória.
(Além de uma penalidade no desempenho, já que abrir uma instância com várias propriedades para atualizar uma propriedade é mais demorado do que uma atualização do SQL)

Portanto, para concluir: tenha cuidado ao abrir instâncias "muito longas" no seu código se também estiver usando SQL.

Anexei uma classe de teste completa. Se quiser ver por si mesmo, chame Do ##class(Test.Customer).Test(0) para ver o código usando somente OO e .Test(1), usando SQL (observe que a atualização do SQL é substituída)
Qualquer comentário é bem-vindo!
 

Class Test.Customer Extends %Persistent
    {
    Property Name As%String;Property Active As%Boolean;ClassMethod ActivateSQL(customerId) As%Status
    {
        #Dim exception
    
        &sql(Update Test.Customer Set Active=1WhereID=:customerId)
        If SQLCODE'=0 {
            Set exception = ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE, $Get(%msg))
            Quit exception.AsStatus()
        }
    
        &sql(SelectName, Active Into :name, :active From Test.Customer WhereID = :customerId)
        Write !,"Result After SQL Update : ",!
        Write"Name   : ",name,!
        Write"Active : ",active,!!
        Quit
    }
    
    ClassMethod ActivateOO(customerId) As%Status
    {
        #Dim objCust as Test.Customer
        #Dim sc as%StatusSet objCust = ##class(Test.Customer).%OpenId(customerId)
        Set objCust.Active = 1Set sc = objCust.%Save()
        If sc'=$$$OKQuit sc
        &sql(SelectName, Active Into :name, :active From Test.Customer WhereID = :customerId)
        Write !,"Result After %Save : ",!
        Write"Name   : ",objCust.Name,!
        Write"Active : ",objCust.Active,!!  
        Quit
    }
    
    ClassMethod Test(mode = 0)
    {
        #Dim objCust as Test.Customer
        #Dim sc as%Status#Dim id as%Integer;Create an instance and keep the id in memorySet objCust = ##class(Test.Customer).%New()
        Set objCust.Name = "Danny"Set sc = objCust.%Save() If sc'=1Write"Could not save",!
        Set id = objCust.%Id()
        Kill objCust
    
        ;Open and display the created instanceSet objCust=##class(Test.Customer).%OpenId(id)
        Write"Name   : ",objCust.Name,!
        Write"Active : ",objCust.Active,!   
    
        ;Chame um classmethod que atualize o id com SQL ou OOIf mode=0 {
            Do objCust.ActivateOO(id)
        } else {
            Do objCust.ActivateSQL(id)
        }   
        ;Mude a instância (que ainda está na memória)Set objCust = ##class(Test.Customer).%OpenId(id)
        Set objCust.Name = objCust.Name_" - edited"Set sc = objCust.%Save() If sc'=1Write"Could not save",!
        Write"Name   : ",objCust.Name,!
        Write"Active : ",objCust.Active,!
        ;a atualização do SQL em classmethod é substituída pela instância que ainda estava na memória;Abra e demonstre a instância criadaKill objCust
        Set objCust = ##class(Test.Customer).%OpenId(id)
        Write"Name   : ",objCust.Name,!
        Write"Active : ",objCust.Active,!
    }
    }