Question
· Oct 4, 2018

Cache Objects -How to check for a null property when reading an object?

You have a global ^CODE("TNO","BIO",291,"AKI") that may or may not exist. On the data side of the global ref it can have  a boolean value of 0 (false) or 1 (true) and this global is wrapped up in a Caché class accessible from myobject.AKI property. At the object level, how do you check whether the property is defined ie. is there a $DATA equivalent for Cache Object properties? Also, how would you kill /null the property as opposed to making the value  0 (false) or an empty string?

Discussion (10)2
Log in or sign up to continue

Steve, what do you mean by "the property is defined"? Property is always defined if you make it in the class definition.

On object side you can only check whether property is empty or has some other value.

  IF myobject.AKI="" { do some other stuff ...}

But you do not know if it is because ^CODE("TNO","BIO",291,"AKI")= "" or if it doesn't exist.

Ok so lets kill off the AKI node in the global and instansiate the object by passing in the IDKEY

k ^CODE("TNO","BIO",291,"AKI")
set myobject= ##class(Code.TestDetails).%OpenId("BIO||291")

If you do if (myobject.AKI="") what are you really asking:

1) if ($GET(^CODE("TNO","BIO",291,"AKI"))="")
2) if ($DATA(^CODE("TNO","BIO",291,"AKI"))=0)

I think it is option 1 and I was wondering if there was a $DATA operation that would work at the object level. I see %DynamicObject has a %IsDefined("propertyName") method but this is more often seen in parsing JSON objects and used to detect { "AKI": null }

Perhaps this is what I'm looking for:

Set isNullBool = ##class(Code.TestDetails).%ObjectIsNull(myobject.AKI)
zwrite isNullBool

Any other thoughts?

No, the internal code will call AKISet when you assign a value to the AKI property. I bashed this out as a registered object, probably had a singleton pattern going on in my mind. On reflection it would be a really bad idea to mix this with a persistent object.

If you really want to wrap legacy data with persistent objects then do it the right way...

https://community.intersystems.com/post/art-mapping-globals-classes-1-3

You can set up ^CODE("TNO","BIO",{BIOID},"AKI") as the storage structure for the AKI property value. For any new objects, it doesn't matter if you assign "1", "0" or "" to that property, it will always create the storage location with one of those three values. The state will never be broken.

There is no point moving to persistent objects if there is legacy code that is bypassing it. It would be an all or nothing solution, everything uses the persistent object or not at all. In which case, when you come to deploy the object and legacy updates, you just need to run a small utility that will add in missing "AKI" nodes with an empty string value.

This would be a much more maintainable approach in the long run, and you will get all the benefits of SQL as well.

It's a real-world scenario where you can have something as true, false or undefined. In C# .NET you might use bool? type for this.  If you are populating a checkbox HTML element, you will have to consider  the three possible outcomes. Since I'm mapping classes to pre-existing global structures it's important to understand how the object properties map to these pre-existing global structures.

You hit the point %Boolean s an excellent example  it can be TRUE, FALSe or NULL
in Caché terms: 1, 0, ""

Your example ^CODE("TNO","BIO",291,"AKI"))  is partially misleading in that sense that a global subscript can NEVER be ""
while the content of $LB() can be $LB(1,2,"",4)  or $lb(1,2,,4)  here you find your  "undefined" again,
similar to NULL in SQL (which is a different story)

I looked at the generated code (I made my own example) and it is:

  i%AKI=$g(^CODE("TNO",i%Type,i%MyID,"AKI"))

Therefore as Robert already wrote, it is option 1).  The property is empty (has empty string) if given node doesn't exist or is set to "".  And from object point you cannot differentiate it.

Even using %ObjectIsNull method you would get same result if the global is killed or if it is set to "".

Question is why do you need such information?

How about something along these lines...

Property AKI As %Boolean;

Property BIOID As %String;

Method AKIGet() As %Boolean [ ServerOnly = 1 ]
{
    If '$Data(^CODE("TNO")) Quit ""
    If '$Data(^CODE("TNO","BIO")) Quit ""
    If '$Data(^CODE("TNO","BIO",..BIOID)) Quit ""
    Quit $Get(^CODE("TNO","BIO",..BIOID,"AKI"))
}

Method AKISet(Arg As %Boolean) As %Status [ ServerOnly = 1 ]
{
    If Arg="" {
        Kill ^CODE("TNO","BIO",..BIOID,"AKI")    
    } Else {
        Set ^CODE("TNO","BIO",..BIOID,"AKI")=Arg
    }
    Quit $$$OK
}

I'm passing in your BIO number as another property, I guess you could use a public variable list.

I've tried to make the getter a little defensive. You could argue that you might also need to check if ..BIOID is empty, because this can cause subscript errors, but then again, maybe you want these to fail anyway so they can be handled upstream.

Sean.