Hi Michael,

This isn't a straight answer to the problem of extracting business logic from CSP pages - but if you haven't already, check out the isc.rest project. Its an open source release of the framework we've been using to migrate our CSP and Zen apps to REST (Angular front end). 

If nothing else, it provides a uniform, safe and easy to maintain way of creating a REST API from an existing application. 

My process has generally been to copy CSP business logic to a class method in the relevant persistent class and expose it as a REST Action there (see Defining Actions in isc-rest user guide).  Its possible to expose any method in this way, so depending on your situation, it could make sense to point an action endpoint to the class methods in CSP directly, or use a wrapper method.

This recent presentation is a great place to start: https://learning.intersystems.com/course/view.php?id=2102

git hub: https://github.com/intersystems/isc-rest

Thank you, @Robert Cemper and @Eduard Lebedyuk!

I did not know about MANAGEDEXTENT, assuming there was no way to use the same storage in another class.  Ed's warning is relevant though, I can't ensure that there wouldn't be other concurrent writes.

Here is my working solution.... Subclassing of the record class with Setters for the 2 properties seems to work.  Simply overriding the 2 properties without [ SqlComputed ] did not change the computed behavior.

Class Audit.HistoricalRecord Extends Audit.Record {

Method LastModifiedTimeSet(value As %String) As %Status
 {
    Set i%LastModifiedTime = value
    quit $$$OK
 }

Method LastModifiedUserSet(value As %String) As %Status
{
    Set i%LastModifiedUser = value
    quit $$$OK
}

}

My migration routine can now create and save objects of the HistoricalRecord class

From the docs on the SQL Gateway
"All the tables listed in the FROM clause of an SQL query must come from the same data source. Queries that join data from heterogeneous data sources are not allowed."
https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?K...

You might be able to use a sub query to work around this:
select * from tableusers u JOIN (SELECT * FROM  tablelogentries) e ON e.userid = u.userid

I would contact the WRC if the above workaround doesn't cover all of your use cases.

Hello Luk,

I have seen "Error Condition: Failed to read posted content from the client" due to a quirk in how IE resends some requests after a network timeout. This discussion on stackoverflow describes the IE behavior I saw that resulted in these messages:

https://stackoverflow.com/questions/4796305/why-does-internet-explorer-not-send-http-post-body-on-ajax-call-after-failure

I would try to isolate what CSP requests are causing the messages.  

Hello Dhaval,

I don't believe that there is a clean way to access the property you are looking for, though you may be able to finagle the data you need in SQL with the $list function.

On the first array level (Employee), Cache projects the property as a table, allowing for easy SQL access.  However, because Employee is not Persistent, it cannot project its address array property as a table.  Any SQL access will have to deal with the $listbuild that the second level array is stored as.

If possible, I would make Employee a stand alone table, maybe as a Relationship rather than an array.  

Hello Mikhail, Good question!

I think of block count as a rough estimate of the disk size of a table or index (an "SQLmap").  The SQL optimizer uses this to get an estimate of how much disk I/O could be involved in scanning that map.  Block count mostly matters in proportion to other block counts (similar to ExtentSize).  

I am not certain how to interpret 2K reference in the documentation, perhaps someone else will chime in - my guess is that the "units" don't matter, so the original 2K block size is still used as a base unit for measuring the disk size of a storage map.

Block Count can be extremely important for tables that have child tables, or that otherwise share a storage global with other classes. The row count might be relatively small, but because the global nodes are spaced out, more disk I/O is required.  With the block count taken into consideration, the SQL optimizer may be pushed toward an index or different starting table.

It is possible that a query can skip the master map completely, but only if all fields being selected are in the index and in the same form as in the master map.  

Even if all fields are in an index, indexed fields are typically stored in SQLUPPER, required for SQL comparisons, but the query must get the fields in their original form for output.  So for a SELECT *, the query will almost always need to go back to the mastermap.