Got a weird RLMException: Invalid property name 'primaryKeyProperty' for class


#1

I have been using Realm for quite a long time and in about 3 - 4 versions ago I have added primaryKey to an old object.
The migration block looks like this:

Realm.Configuration(encryptionKey: REALM_KEY,
                                         schemaVersion: 3,
                                         migrationBlock: {
                                            migartion, oldSchemaVersion in
                                            if oldSchemaVersion < 2 {
                                                migartion.enumerateObjects(ofType: DBObject.className(), {
                                                    oldObject, newObject in
                                                    
                                                    newObject?["primaryKeyProperty"] = "dbReferenceID"
                                                })
                                            }
                                        },
                                         deleteRealmIfMigrationNeeded: false)

The migration has been working fine until today that I have received a crash log from the client showing the following problem:

Fatal Exception: RLMException
Invalid property name 'primaryKeyProperty' for class 'DBObject'.

I am wondering if I have mis-used the migration to add primaryKey to old object, but I have tested using old version of my application which works fine.


#2

Does that crash occur within the migration block or perhaps within the DBObject class

override static func primaryKey()

function?


#3

The crash occur within the migration block.


#4

Looking at your code, there’s an issue which may or may not be intentional.

newObject?["primaryKeyProperty"] = "dbReferenceID"

That won’t work; primary keys must be unique and assigning “dbReferenceID”` to each object would be a duplicate.

Let’s assume though, you just used that as a placeholder in your question.

I created a Car object with a color property and added them to realm

I then updated my Car object like this

class CarObject: Object {
    @objc dynamic var color = ""
    @objc dynamic var primaryKeyProperty = ""
    override static func primaryKey() -> String? {
        return "primaryKeyProperty"
    }
}

and then tested it with the following migration block

Realm.Configuration.defaultConfiguration = Realm.Configuration(
    schemaVersion: 1,
    migrationBlock: { migration, oldSchemaVersion in
        if (oldSchemaVersion < 1) {
            migration.enumerateObjects(ofType: CarObject.className()) { oldObject, newObject in
                newObject?["primaryKeyProperty"] = NSUUID().uuidString
            }
        }
})

and it worked correctly and added the NSUUID().uuidString primary key to each car object.


#5

I think you have misunderstood the usage of my code.
Instead of having a variable named “primaryKeyProperty” and assigning a value to it, the migration block is an old method to assign a primaryKey to an existing object.
Though after searching for a while, this method is useless now as Realm will automatically update the primaryKey as mentioned here:

Anyway, the problem is solved and thanks for the reply.


#6

Glad it’s working and I actually did get usage.

Let me add this bit of info for future clarity… In the SO post you linked,

You don’t need to attempt change primary key in the migration block.
We always automatically update the schema to the latest version, and the only thing that you have to handle in the migration block is adjusting your data to fit it (e.g. if you rename a property you have to copy the data from the old property to the new one in the migration block).

What that means is that any properties added to a Realm Object ARE automatically added to the Realm. So you don’t need to define it in the migration block.

What’s happening in that post that that you’re telling Realm to add a new property to it’s object and it will become the primary key by changing this

override static func primaryKey() -> String? {
    return "registrationPlate"
} ...

to this

override static func primaryKey() -> String? {
    return "plateID"
} ....

However, primary keys are unique and required so if you don’t provide values to it, then you’ll have nil primary keys, which are not allowed.

To maintain all of the old primary key data, the migration block is copying all of the old registrationPlate data into the new plateID data with this

newObject!["plateID"] = Int(oldObject!["registrationPlate"] as! String)

but here’s the important bit… ID’s must be unique which is why the answer says that if you want to use a sequential number (instead of copying the old registration plate data) use this

var plateID = 0
migration.enumerate(RegistrationPlateDB.className()) { oldObject, newObject in
    newObject!["user"] = oldObject!["user"]
    newObject!["plateID"] = plateID
    plateID += 1
}

That will automatically assign a sequential numerical key to the new primaryKey.

See how plateID increments by 1 each time through the iteration? Those will each be unique values. Whereas this

newObject?["primaryKeyProperty"] = "dbReferenceID"

would not be creating unique ID’s - they would all be ‘dbReferenceID’

So the migration block code would definitely be needed if you either want to maintain your existing primary key data OR if you want to change the primary key data to sequential numeric. The code in my answer proposed to use the UUID instead of numeric but that depends on the use case.