Objectbox-java: ToOne.isNull returning false when target doesn't exist

Created on 8 Jul 2019  路  5Comments  路  Source: objectbox/objectbox-java

Issue Basics

  • ObjectBox version (are using the latest version?): 2.3.2
  • Reproducibility: always

Reproducing the bug

Description

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.

Entities

@Entity
data class A(
    @Id
    var dbId: Long = 0,
) {
    lateinit var content: ToOne<B>
}


@Entity
data class B(
    @Id
    var dbId: Long = 0,
)

Misc

Apologies if this is expected behaviour, but it seems counter intuitive to me. The workaround is simple, just check if target is null.

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 isNull isn't working.

I'll try to reproduce it and post the code.

All 5 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rohitgupta1694 picture rohitgupta1694  路  4Comments

Mister-Seven picture Mister-Seven  路  3Comments

santalu picture santalu  路  3Comments

aahlenst picture aahlenst  路  5Comments

squeeish picture squeeish  路  5Comments