Realm-cocoa: Compound Key

Created on 7 Dec 2014  路  21Comments  路  Source: realm/realm-cocoa

It is possible to have a compound key or a composite key (a primary key made up of two columns)?

T-Help

Most helpful comment

Neither of those will accomplish what you're trying to do. The lazy property will only have the appropriate value the first time you access it. Afterwards, once id or type is modified, compoundKey won't be updated.

You could use a mix of lazy and didSet instead to have the compoundKey property be both _derived_ and _stored_:

public final class Card: Object {
    public dynamic var id = 0 {
        didSet {
            compoundKey = compoundKeyValue()
        }
    }
    public dynamic var type = "" {
        didSet {
            compoundKey = compoundKeyValue()
        }
    }
    public dynamic lazy var compoundKey: String = self.compoundKeyValue()
    public override static func primaryKey() -> String? {
        return "compoundKey"
    }

    private func compoundKeyValue() -> String {
        return "\(id)-\(type)"
    }
}

// Example

let card = Card()
card.id = 42
card.type = "yolo"
card.compoundKey // => "42-yolo"

Edit (on 24 Jun 2016 by @mrackwitz): Please refer to https://github.com/realm/realm-cocoa/issues/1192#issuecomment-110468780 for an updated workaround.

All 21 comments

Sure! You can do this by combining two keys in one. For example, say you have a Person model with firstName and lastName string properties, you could add a third fullName property as a derived-value, compound key. You could even make this the primary key.

Depending on the type of data in both columns, you could even just store it once and have computed properties to extract both when you want to use them. In our previous Person example, firstName and lastName could be computed from fullName if we simply stored them inline separated by a reference character (i.e. "\u{0}").

I'm closing this issue as there are simple ways to implement this feature user-side. We'll re-open it if we feel it's a feature worth implementing.

I use a computed property as the primaryKey like this:

public final class Card: Object{
  public dynamic var id = 0
  public dynamic var type = ""
  public dynamic var compoundKey: String { return "\(id)-\(type)" }
  override public static func primaryKey() -> String? {
    return "compoundKey"
  }
}

However I get an error "RLMException", "Primary key property 'compoundKey' does not exist on object 'Card'"

Got it working by using lazy var

   public dynamic lazy var compoundKey: String = "\(self.id)-\(self.type)"

But not sure if this is the correct way to do it..

Neither of those will accomplish what you're trying to do. The lazy property will only have the appropriate value the first time you access it. Afterwards, once id or type is modified, compoundKey won't be updated.

You could use a mix of lazy and didSet instead to have the compoundKey property be both _derived_ and _stored_:

public final class Card: Object {
    public dynamic var id = 0 {
        didSet {
            compoundKey = compoundKeyValue()
        }
    }
    public dynamic var type = "" {
        didSet {
            compoundKey = compoundKeyValue()
        }
    }
    public dynamic lazy var compoundKey: String = self.compoundKeyValue()
    public override static func primaryKey() -> String? {
        return "compoundKey"
    }

    private func compoundKeyValue() -> String {
        return "\(id)-\(type)"
    }
}

// Example

let card = Card()
card.id = 42
card.type = "yolo"
card.compoundKey // => "42-yolo"

Edit (on 24 Jun 2016 by @mrackwitz): Please refer to https://github.com/realm/realm-cocoa/issues/1192#issuecomment-110468780 for an updated workaround.

@jpsim thanks!

@jpsim I have tried your compoundKey example. But I'm getting an exception saying that compoundKey can not be set to "-" which means that it tries to call compoundKeyValue() before the other properties are set. Also I have found that didSet is never called when I use create method and give it a JSON value. Is there any workaround to have compoundKey and create object by JSON value at the same time?

I forgot that Realm won't actually call willSet and didSet once those objects are persisted, so you'll have to use custom setters:

public final class Card: Object {
    public dynamic var id = 0
    public func setCompoundID(id: Int) {
        self.id = id
        compoundKey = compoundKeyValue()
    }
    public dynamic var type = ""
    public func setCompoundType(type: String) {
        self.type = type
        compoundKey = compoundKeyValue()
    }
    public dynamic var compoundKey: String = "0-"
    public override static func primaryKey() -> String? {
        return "compoundKey"
    }

    private func compoundKeyValue() -> String {
        return "\(id)-\(type)"
    }
}

// Example

let card = Card()
card.setCompoundID(42)
card.setCompoundType("yolo")
card.compoundKey // => "42-yolo"

Edit (on 24 Jun 2016 by @mrackwitz): This post was edited to avoid using lazy to reflect the fact that using this on managed properties from Realm Swift is now explicitly forbidden as seen in #3337.

Thank you for the reply. Although this solution will work, but then I have to
1. Create the object let card = Card()
2. Set its properties. card.setCompoundID(42); card.setCompoundType("yolo")
3. Add it to Realm.
For each Object and its child objects I have, which is alot of work to do (I have a realy big json with many objects). The best thing I loved about realm is being able to create object using its json in just one line.
Is there anyway leads to not loosing using create object by json value?

you could update the compound key property after doing a json import.

Core Data announced the "Unique Constraints" feature in WWDC 2015 which make these problems solved (WWDC 2015 220).

It would be so great if Realm can have something like that other than just primaryKey.

Can anyone explain to me how I can define compound keys in Objective-C, using realm? I am unable to make this work. I keep getting this error _"'RLMException', reason: 'Missing property value'"_ for my compound primary key, when trying to implement the solution proposed above. Obviously I'm missing the core concept here.

Thanks.

+1 @phaphan I'm at the same point

To recap: Realm doesn't support treating two keys as one (compound keys), but you can easily store the combined value of two keys in a third, and then operate on that manually combined key.

class Person: Object {
  dynamic var firstName = ""
  dynamic var lastName = ""
  dynamic var fullName = "" // "compound" key
}

// later

let realm = Realm()
let person = Person()
person.firstName = "John"
person.lastName = "Doe"
person.fullName = "\(person.firstName) \(person.lastName)"
realm.write {
  realm.add(person)
}
realm.objects(Person).filter("fullName = 'John Doe'") // => can query the compound key (it's just a regular property)

Some of the code samples posted above aim to automate these operations to keep the compound key in sync with its dependent keys, but that's entirely up to you to maintain.

Thanks @jpsim , your reply clarifies this matter, and I get the idea behind the implementation you propose. However, I was looking for a neat automated way of performing a _createOrUpdateInDefaultRealmWithValue_ operation from a dictionary (JSON response) object,
e.g. by

for (NSDictionary *object in objects) {
     [RLMObject createOrUpdateInDefaultRealmWithValue:object];
}

without having to manually derive a compound key for each object. In other words, I was looking for a way of instructing my object model to do this by itself ;)

class Person: Object {
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var fullName = "" // "compound" key
}

// later

let realm = Realm()
let person = Person()
person.firstName = "John"
person.lastName = "Doe"
person.fullName = "(person.firstName) (person.lastName)"
realm.write {
realm.add(person)
}

so please let me know
person.fullName = "(person.firstName) (person.lastName)"
can I used this way for composite key or not ? and if not then not ?

Thanks in advance!

Yes, doing double bookkeeping will essentially provide you with a "compound" key.

This doesn't work for me:

class ChatUpdateModel: Object {
    dynamic lazy var id: String = self.getId()
    dynamic var feedType: NSNumber = 0
    dynamic var feedId: String = ""

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

    func getId () -> String {
        return String(feedType) + feedId
    }

    func load (fromChatUpdateEntity update: ChatUpdateEntity) {
        feedType  = NSNumber(longLong: update.feedType)
        feedId    = update.feedId
        id = getId()
    }
}

let updateModel = ChatUpdateModel()
updateModel.load(fromChatUpdateEntity: update)

try! realm.write {
    if let _ = realm.objectForPrimaryKey(ChatUpdateModel.self, key: updateModel.id) {
        realm.add(updateModel, update: true)
    } else {
        // here I get EXC_BAD_ACCESS in function "RLMAddObjectToRealm" on line 203:
        // value = static_cast<RLMOptionalBase *>(object_getIvar(object, prop.swiftIvar)).underlyingValue;
        realm.add(updateModel)
    }
}

Can you help me please?

@sokal32 looks like your issue may be fixed by #2901, although that implementation is still a work in progress. In the future, please file new issues as new issues on GitHub. Commenting on unrelated, months-old issues is easy for us to miss.

Please note that beginning from Realm Swift 1.0.1, you can't use lazy properties anymore due to interoperability issues with the Objective-C runtime as seen in #3337. That means if you declared your compound key variable as lazy, you will need to drop this attribute from your property declaration after updating. Doing that will forbid to use an instance member to calculate the default value, so that you can't use compoundKeyValue() there anymore. The default value you specify instead should be equal to the value, you would get when calling compoundKeyValue() on a fresh created instance without setting any properties. Given - is used to separate the components in the compound value, these could be the following values: for 0 & "" => 0-, "" & "" => -, 0 & -1 => 0--1, etc.

public final class Card: Object {
    public dynamic var id = 0
    public func setCompoundID(id: Int) {
        self.id = id
        compoundKey = compoundKeyValue()
    }
    public dynamic var type = ""
    public func setCompoundType(type: String) {
        self.type = type
        compoundKey = compoundKeyValue() 
    }
-   public dynamic lazy var compoundKey: String = self.compoundKeyValue()
+   public dynamic var compoundKey: String = "0-"
    public override static func primaryKey() -> String? {
        return "compoundKey"
    }

    private func compoundKeyValue() -> String {
        return "\(id)-\(type)"
    }
}

I can't do it in Objective C, it always throws exceptions. anybody tried OC version?

Reiterating what @mrackwitz said: "beginning from Realm Swift 1.0.1, you can't use lazy properties anymore due to interoperability issues with the Objective-C runtime as seen in #3337". I have however found an easy way of creating a compound key as follows:

class StoreSupplierMap: Object{
    dynamic var storeId = ""
    dynamic var supplierId = ""
    dynamic var compoundKey = ""


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

    func configure(storeId: String, supplierId: String){
        self.storeId = storeId
        self.supplierId = supplierId
        self.compoundKey = self.storeId + self.supplierId

    }

}

A sample implementation can be as follows:

let storeSupplierMap = StoreSupplierMap()
storeSupplierMap.configure(storeId: "abc", supplierId: xyz) 
print(storeSupplierMap.compoundKey) //=> abcxyz
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ishidakei picture ishidakei  路  3Comments

TheHmmka picture TheHmmka  路  3Comments

jpsim picture jpsim  路  3Comments

i-schuetz picture i-schuetz  路  3Comments

duribreux picture duribreux  路  3Comments