19

I have a object Person with many dogs. App has separate page where it shows just dogs and other page where it shows person's dogs

My model is as follows

class Person: Object {
    dynamic var id = 0
    let dogs= List<Dog>()

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

class Dog: Object {
    dynamic var id = 0
    dynamic var name = ""

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

I have persons stored in Realm. Person has detail page where we fetch and show his dogs. If dog already exist, I update latest info for that dog and add it to person's dog list else create new dog, save it and add it to persons list. This works in coredata.

// Fetch and parse dogs
if let person = realm.objects(Person.self).filter("id =\(personID)").first {
    for (_, dict): (String, JSON) in response {
        // Create dog using the dict info,my custom init method
        if let dog = Dog(dict: dict) {
            try! realm.write {
                // save it to realm
                realm.create(Dog, value:dog, update: true)
                // append dog to person
                person.dogs.append(dog)
            }
        }
    }
    try! realm.write {
        // save person
        realm.create(Person.self, value: person, update: true)
    }
}

On trying to update person with his dogs,realm throws exception Can't create object with existing primary key value

Dan
  • 1,274
  • 2
  • 15
  • 32
Deekshith Bellare
  • 725
  • 2
  • 6
  • 19
  • 1
    Where is the `recipe` value in `person.dogs.append(recipe)` coming from? It's not really clear what you're trying to do here, sorry! – TiM Nov 14 '16 at 18:00
  • Sorry, it must be person.dogs.append(dog). I am just creating/updating dog and adding it to the dogs of person @TiM – Deekshith Bellare Nov 14 '16 at 18:05
  • 1
    Ahh okay. I think I see what's going on here now. I'll add an answer. :) – TiM Nov 14 '16 at 18:08

2 Answers2

35

The problem here is that even though you're creating a completely new Realm Dog object, you're not actually persisting that one to the database, and so when you call append, you're trying to add a second copy.

When you call realm.create(Dog.self, value:dog, update: true), if an object with that ID already exists in the database, you're simply updating that existing object in with the values in the dog instance you created, but that dog instance is still an independent copy; it's not the Dog object in the database. You can confirm this by checking if dog.realm is equal to nil or not.

So when you call person.dogs.append(dog), because dog is not already in the database, Realm tries to create a whole new database entry, but fails because there is already a dog with that ID.

If you want to append that dog object to a person, it'll be necessary to query Realm to retrieve a proper dog object that's referencing the entry in the database. Thankfully this is really easy with Realm objects backed by primary keys since you can use the Realm.object(ofType:forPrimaryKey:) method:

if let person = realm.object(ofType: Person.self, forPrimaryKey: "id") {
    for (_, dict): (String, JSON) in response {
        //Create dog using the dict info,my custom init method
        if let dog = Dog(dict: dict)
        {
            try! realm.write {
                //save it to realm
                realm.create(Dog.self, value: dog, update: true)
                //get the dog reference from the database
                let realmDog = realm.object(ofType: Dog.self, forPrimaryKey: "id")
                //append dog to person
                person.dogs.append(realmDog)
            }
        }
    }
    try! realm.write {
        //save person
        realm.create(person .self, value: collection, update: true)
    }
}
TiM
  • 15,812
  • 4
  • 51
  • 79
  • In my case i have 6 events to save which has unique type each. Im saving all those 6 events in job table. But when Im trying to create one more job object with different job id, When im creating first event fir second job. Im getting error. can't create object with same primary key. – siva krishna Dec 07 '17 at 09:44
  • If you must, you might need to query the database to see if an object with the same primary key already exists. That error only occurs when you try to add a second object with the same primary key, so you just need to make sure your logic isn't doing that. :) – TiM Dec 09 '17 at 09:29
8

Latest API solution:

Use add(_:update:).

try realm.write {
    realm.add(objects, update: Realm.UpdatePolicy.modified)
    // OR
    realm.add(object, update: .modified)
}

Realm.UpdatePolicy enums:

error (default)
modified //Overwrite only properties in the existing object which are different from the new values.
all //Overwrite all properties in the existing object with the new values, even if they have not changed

NB: Works on Realm Swift 3.16.1

Lal Krishna
  • 15,485
  • 6
  • 64
  • 84