How do I delete Realm objects which are not referenced by any other object during migration?

1.7k Views Asked by At

I have two models -

class Direction : RealmObject {
    var distance : Int
    var polyline : String
}

class Route  : RealmObject {
    var id : String
    var directionList : RealmList<Direction>
}

I have been using insertOrUpdate() to update Route class with the assumption that when I call it, the existing direction objects in directionList were removed and replaced with the new list I provided. However, I recently discovered that that is not happening. Even cascade delete is not supported when I call route.deleteFromRealm(). So now I've ended up with hundreds of objects in Direction table with no objects referring to them.

How can I remove all those objects from Direction class which have no Route objects referring to them in Realm migration?

3

There are 3 best solutions below

3
Chris Shaw On

Two possible ways I can think of.

The first may not help you at this time, but it can in future perhaps. By adding a LinkingObjects property to the Direction class, you can let the model determine which Direction objects have no related Route objects. LinkingObjects is described here (https://realm.io/docs/java/5.8.0/api/io/realm/annotations/LinkingObjects.html). With a property on Direction: E.g.:

\@LinkingObjects("direction")
final RealmResults<Route> routes = null;

Then you can delete any objects where:

RealmResults<Direction> unusedDirections = realm.where(Direction.class).isEmpty("routes").findAll();

You may need to do this for your next release though.

A second way is more long winded, but essentially:

  1. Find all Direction objects: RealmResults<Direction> redundantDirections = realm.where(Direction.class).findAll();
  2. Find all Route objects (similar to above).
  3. Iterate through all Route objects.
  4. Filter the redundantDirections query to exclude any Direction objects referenced by each Route object.
  5. Delete the final redundantDirections.

I'd hope there's a third way I'm unaware of.....

0
vepzfe On

This is the way I solved it -

override fun migrate(realm: DynamicRealm, oldVersion1: Long, newVersion: Long) {

    if (oldVersion == 2L) {

        val routeSchema = schema.get("Route")
        val directionSchema = schema.get("Direction")

        /*
        Creating a new temp field called isLinked which is set to true for those which are
        references by Route objects. Rest of them are set to false. Then removing all
        those which are false and hence duplicate and unnecessary. Then removing the temp field
        isLinked
         */
        directionSchema!!
                .addField("isLinked", Boolean::class.java)
                .transform { obj ->
                    //Setting to false for all by default
                    obj.set("isLinked", false)
                }

        routeSchema!!.transform { obj ->
            obj.getList("directionList").forEach {
                //Setting to true for those which are referenced
                it.set("isLinked", true)
            }
        }

        //Removing all those which are set as false
        realm.where("Direction")
                .equalTo("isLinked", false)
                .findAll()?.deleteAllFromRealm()

        directionSchema.removeField("isLinked")

        //Rest of the migration
    }
}

There are some more things I discovered. According to this very informative talk on Realm - https://academy.realm.io/posts/jp-simard-realm-core-database-engine/ (skip to 28:45), there is way to remove all those unreferenced nodes from the B-Tree. However, I could not find a way to do that. Realm.compactRealm() seemed like a way to do that but it did not work.

0
EpicPandaForce On

How can I remove all those objects from Direction class which have no Route objects referring to them in Realm migration?

Should be easy using DynamicRealmObject.linkingObjects.

val routes = realm.where("Destination").findAll()
routes.createSnapshot().forEach { route ->
    val linkingObjects = route.linkingObjects("Route", "directionList")
    if(linkingObjects.isEmpty()) {
        route.deleteFromRealm()
    }
}

This should theoretically work