When you have an entity that defines a ToOne relation to another entity, you can use isNull to check if the other entity is null. However, this does not work properly if the other entity does not exist in the database anymore.
For instance, you have an entity A that defines a ToOne relation to entity B. If you delete B, entity A will still retain the ID and so isNull will incorrectly return false. But when accessing entity B with target, the result will be null.
@Entity
data class A(
@Id
var dbId: Long = 0,
) {
lateinit var content: ToOne<B>
}
@Entity
data class B(
@Id
var dbId: Long = 0,
)
Apologies if this is expected behaviour, but it seems counter intuitive to me. The workaround is simple, just check if target is null.
This is somewhat expected behavior, let me explain. The core principle in ObjectBox: once you get an entity from a box it has a snapshot of its current data. You have to get or query the entity again to get fresh data. This makes it safe to work with data without it unexpectedly changing or accessing the database.
Now, with ToOne this is less transparent because when you get A ObjectBox does not actually fetch the whole entity B, but just its ID. It's stored in contentId of A (added to the bytecode of A by the Gradle plugin).
Then ToOne.isNull() returns false as a target ID is set, even if the cached target is null:
https://github.com/objectbox/objectbox-java/blob/e38d33bbba6375011230c8930cb08e8ce5c8b8ad/objectbox-java/src/main/java/io/objectbox/relation/ToOne.java#L147-L149
I suppose this is a documentation (or lack of it) issue on the ToOne.isNull() and related methods.
As you found out it is best to access content.target which will get B from the database. Though note that once accessed it will be cached. To get fresh data you have to get a fresh copy of A from the database.
Hmm okay I guess that makes sense yes. Though I do think there should be some method to remedy this.
If I understand correctly from my googling, I need to manually make sure to set the targetId of A to 0 when deleting B, otherwise A will still retain the id and isNull will have the behaviour described above and, IMO, will introduce inconsistency in the database.
Just thinking out loud here, but maybe this could be solved by checking for a @Backlink when deleting B (in this case), and setting the targetId in A to 0?
If you remove B it should be removed from all relations. You just have to load A from the database again after removing B and the targetId should be 0.
var a = aBox.get(aId)
val b = a.content.target
bBox.remove(b)
a = aBox.get(aId)
assert(a.content.target == null)
Hmm, in that case there appears to be a bug present. All of my logic is reactive (RxJava) and I only do operations on items when ObjectBox notifies me of changes via rx. And, even after restarting my app, the ids are still present in "A", and therefore isNull isn't working.
I'll try to reproduce it and post the code.
Closing this issue due to inactivity. :zzz: Feel free to re-open with more details or submit a new issue.
Most helpful comment
Hmm, in that case there appears to be a bug present. All of my logic is reactive (RxJava) and I only do operations on items when ObjectBox notifies me of changes via rx. And, even after restarting my app, the ids are still present in "A", and therefore
isNullisn't working.I'll try to reproduce it and post the code.