Get class property position in storage $lb

Caché, Globals

I have a classname and a property name. I want to know, when I get object value directly from a global, which property corresponds to which position in the $lb structure.

Here's what I got so far:

Class Utils.Storage {

/// write ##class(Utils.Storage).GetPosition()
ClassMethod GetPosition(class As %Dictionary.CacheClassname = "Sample.Address", property As %Dictionary.CacheIdentifier = "Zip") As %Integer
{
    set strategy = $$$comClassKeyGet(class, $$$cCLASSstoragestrategy)    
    set strategyId  = class _ "||" _ strategy
    &sql(SELECT Name INTO :position
         FROM %Dictionary.StorageDataValueDefinition
         WHERE parent = (SELECT ID
                         FROM %Dictionary.StorageDataDefinition
                         WHERE parent = :strategyId AND Structure = 'listnode')
         AND Value = :property)
    quit position
}


/// do ##class(Utils.Storage).Test()
ClassMethod Test()
{
    set list = $lb("Street", "City", "State", "Zip")
    set obj = ##class(Sample.Address).%New()
    Do obj.%SetSerial(list)
    
    set list2 = obj.%GetSerial()
    zw obj, list2
}
}

Work example:

write ##class(Utils.Storage).GetPosition("Sample.Address","City")
>2

It should work for everything, except:

  • Array Of properties in persistent classes with default array STORAGEDEFAULT
  • List Of properties in persistent classes with non-default array STORAGEDEFAULT
  • Custom storage
  • SQL storage
  • Relationships

And that's okay, I only really need it for serial classes. But is there maybe a better way to get it?

 

UPD: Should probably check how it works with inheritance.

  • + 3
  • 0
  • 348
  • 5
  • 1

Answers

Hi Eduard !

Being a little bit lazy I simplified the query (for less typing)
My example: 

SELECT * FROM %Dictionary.StorageDataValueDefinition
where id [ 'Sample.Person'

And it works fine for persistent and serial classes.
My hidden assumption: there is only 1 Storage Strategy.

The nice point about:
you get also storage locations of deleted (!!) properties  
that eventually might be invisible in class definition.
 
yes yes

Comments

Hi Eduard!

Without going too deep into your code and trying to enhance it, I can suggest you to:

  1. Put this method on a utility class
  2. Recode it so that it is a method generator. The method would use a variation of your current code to produce a $Case() that would simply return the position of a given property instead of querying the class definition in runtime.
  3. Make the class you want to use it (ex: Sample.Address) inherit from it.

Of course, if this is a one-time thing, you could simply implement number 2. Or you could simply forget about my suggestion at all if performance isn't an issue for this scenario. By using a method generator, you eliminates from the runtime any inefficiency of the code used to (pre)compute the positions.

Kind regards,

AS

Hello, Amir!

  1. That was just a quick testing in the existing class. For the actual project of course I would write this method in a separate utility class. you are completely right on that. Still, I edited my code to avoid bad examples.
  2. I  need a method to return storage position of property in $lb at runtime, and I also can't modify existing classes to achieve that (for example by generating a method for each class with precomputed property name=storage position values).
  3. Impossible, because 2.

 

That said I do agree that idea with method generators is good, just not applicable in this exact case.

Hi Eduard,

I fail to follow your reasoning. If you are making other classes inherit from a class of yours, you are changing the class by definition. You are adding a new method to the class. Whether this method is a simple method or has its code computed during class compilation is transparent to whomever is calling that method.

And the resulting method could return the information in any format you choose, including $LB.

Kind regards,

AS

I'm not adding any method to existing classes or adding new superclass.