Using the Buffer property of the FileManager in a Process to Check for Changed

I have a process that runs through a table and updates some records and I want to save the record and execute some other action only if something has changed. I thought of using the Buffer property, which works well on a form procedure.

I have this code in ThisProcess.TakeRecord PROCEDURE

ReturnValue = PARENT.TakeRecord()
SELF.Primary &= Relate:MyTable
DoSomeStuff()  ! Do something that may or may not change some fields 
IF NOT Self.Primary.Me.EqualBuffer(Self.Saved)    ! This produces Unknown function label error during compile
       PUT(Process:View)
END

but I’m getting Unknown function label error during compile. Any suggestions how I can make it work?

I suspect the problem is that SELF.Saved would refer to a Saved property of the ProcessClass (which doesn’t exist), when you probably actually want SELF.Primary.Me.Saved (the save queue in the filemanager class)

You could do like below and simply save a copy of the Record before as a STRING, then compare. This would not check MEMO’s, but you could do similar code.

My:Record_Before STRING(SIZE(My:Record)),AUTO
   CODE
  ReturnValue = PARENT.TakeRecord()
  My:Record_Before = My:Record
  DoSomeStuff()  ! Do something that may or may not change some fields 
  IF  My:Record <> My:Record_Before THEN !did something change?
      PUT(Process:View)
  END

Since you do not need the individual RECORD fields the “Before” can be a STRING, an alternative is LIKE(My:Record) has fields.

Thands Jon and Carl. The problem was that FileManager does not have “Saved” property and I wrongly assumed that it does as all examples use SELF.Saved. I thought of comparing records before and after but wanted to use native FileManager methods as this code is really well tested and it supports MEMO’s as well.

So I need to declare a variable in the data section to store the idenitifier returned from SaveBuffer method

SavedBuffer          USHORT 

Create a reference assignment in the init method after opening files - this step is probably not necessary as I could just call Relate:MyTable.SaveBuffer() instead of SELF.Primary.Me.SaveBuffer().

SELF.Primary &= Relate:MyTable

The Me property is a reference to the FileManager object for the RelationManager’s primary file, therefore FileManager methods like SELF.Primary.Me.SaveBuffer() can be used

So the actual code in TakeRecord looks like this

ReturnValue = PARENT.TakeRecord()
SavedBuffer = SELF.Primary.Me.SaveBuffer()
DoSomeStuff()  ! Do something that may or may not change some fields 
IF NOT SELF.Primary.Me.EqualBuffer(SavedBuffer) ! did something change?
       PUT(Process:View)
END
SELF.Primary.Me.RestoreBuffer(SavedBuffer, False)  ! The RestoreBuffer method releases memory allocated by the SaveBuffer,  value of zero (0 or False) does not update the file's Buffer 

Since the “Buffers” property of the FileManager is a reference to a QUEUE, RestoreBuffer method needs to be called to prevent a memory leak.

This code works fine now, only updating records that have changed.

1 Like

That’s pretty neat use of an awkward class. The lack of an update/put method is apparent.

I agree with Jeff that’s a good example of using those Buffer methods. It also adapts to whatever file is being processed i.e. “Primary”.

You said this is a Process so you are processing many Records through this ABC SaveBuffer() + EqualBuffer() + RestoreBuffer()? Your ABC way will run slower than my simple code, so if you desire for your Process to finish faster try my code… ABC does have the advantage of handling MEMO’s.

My way declares a Save Buffer once at the start My:Record_Before STRING(SIZE(My:Record)). The ABC way for every record processed has to New() a Save Buffer and Add it to a Queue, then undo that with a Dispose and Delete(Q). The EqualBuffer() runs more code than my simple IF My:Record<>My:Record_Before for every record. It does the exact same thing, but it allows multiple save buffers, which you only need the one.

Memory operations with reasonable sizes happen very fast, so I doubt the ABC code will see much slow down in the 1000’s of records, but in the 100,000’s I would think it would matter.

Thanks for publishing this updated code. The supplied documentation for these methods is unfathomable for me and yet it is a technique that we should be able to use regularly.

I suspect that in reality many of us are now using SQL databases and in many cases this sort of processing can be replaced with a single call to the backend?

Correct. SQL Server Stored Procedures are perfect for some Clarion processes because the data doesnt have to be hauled over the network to be processed by the workstation app. This frees up the network helping to maintain responsiveness for other workstation apps, and Stored Procedures are probably the fastest way to process data and make changes to it, out of all the different ways data can be processed.

Absolutely, doing this on the server if you had a SQL backend would be a no brainer-- easier to write, many times quicker to run both because of the lack of network traffic, as Richard mentioned, and because you would not be doing things slow-by-clow (i.e. one row at a time). It would be something like:

update main set <columns> = <TRN.columns> 
where main:column <> trn:column or main.column2 <> trn.column2 ....,ChangeFlag = 1

update somethothertable
set ....
where SomeID in (select ID for main where ChangeFlag = 1;

update main set ChangeFlag = null;

That would be the pure SQL approach. In a procedure you could also use an extension like RETURNING to store the affected IDs in an array or something, and then use that to determine which someothertable rows got updated, rather than set a flag in the main table to show which rows had been affected.