Realm Primary Keys Tutorial

What are Primary Keys?

In a lot of database schema models, being able to track specific rows in the database is a very common usage pattern.

Any developer who’s taken Relational Databases 101 will have heard of a primary key. In its most common form, it is an auto-incrementing integer value used to uniquely identify a row in a table. Rows from other tables can then be referenced via their primary keys.

Realm also features primary keys, but as Realm isn’t a relational database, the way they can be used is a little different.

Actually, since Realm is an object database, the term “primary key” is a bit of a misnomer. In fact, there was a lot of discussion a long time ago that the primary key should be renamed “object ID”. However it was decided that since “object ID” isn’t a term that really represents the concept of the primary key as well as, well ‘primary key’, it was left as is.

Primary keys in Realm are probably exactly what you’ve already guessed. They are a specially designated property that can then be used to uniquely identify specific objects in a Realm database. They’re not required at all to use Realm, but taking advantage of them can unlock some pretty powerful features in Realm.

How do I set them up?

Primary keys are off by default in Realm. It’s up to you as the developer to choose in which object types you would like to enable them.

Enabling them is very easy. Firstly, you add a property to your Realm model class that you would like to use as a primary key. After that, for Cocoa you simply override the ‘primaryKey()’ class method of the Realm Object to declare to Realm that this is your designated primary key. Conversely, Java and .NET make use of annotations to designate the primary key.

Objective-C:


@interface Person : RLMObject
@property NSString *personID;
@property NSString *name;
@end

@implementation Person
+ (NSString *)primaryKey {
    return @"personID";
}
@end

Get more development news like this

Swift:


class Person: Object {
  dynamic var personID = UUID().uuidString
  dynamic var name = ""

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

Java:


public class Person extends RealmObject {

    /* 
       In Java this is done by annotating the field with @PrimaryKey.  Adding
       the primary key annotation also will @Index the field for quick retrieval.
       Since a default value cannot be set in Java, and we'd like to avoid saving
       a person with a null personID, it's usually a good idea to also specify 
       @Required on the field as shown in this example.
    */
    
    @PrimaryKey
    @Required // optional, but recommended.
    private String personID;

    private String name;

    // getters / setters
    
}

C#:

/* 
   In C# this is done by annotating the field with [PrimaryKey] which also implies they are indexed.
*/
class Person : RealmObject
{
    [PrimaryKey]
    public string PersonID { get; set; } = Guid.NewGuid().ToString();
    public string Name { get; set; }
}

Realm primary keys can be any indexable data type you’d like, most commonly integers and strings. Our best practice is to recommend that you use a string property since it’s easier to generate values that are guaranteed to be unique (Especially with the UUID classes in the Apple and .NET frameworks).

Once a primary key value has been set on a specific object, it cannot ever be changed. If another separate object with the same primary key is attempted to be added as a separate object to the Realm, an exception will be triggered.

How do I query for objects with them?

Once you’ve added a primary key to an object type, if you’re using Cocoa, you no longer have to do any manual querying or sorting to find that object; you can fetch it from the database in one simple method call.

Objective-C:


NSString *myPrimaryKey = "Primary-Key";
Person *specificPerson = [Person objectForPrimaryKey: myPrimaryKey];

Swift:


let myPrimaryKey = "Primary-Key"
let realm = try! Realm()
let specificPerson = realm.object(ofType: Person.self, forPrimaryKey: myPrimaryKey)

Java:


// In Java, you query just like you would for any property.        
String myPrimaryKey = "Primary-Key";
Person specificPerson = realm.where(Person.class)
                             .equalTo("personID", myPrimaryKey)
                             .findFirst();
// or asynchronously
Person specificPerson = realm.where(Person.class)
                             .equalTo("personID", myPrimaryKey)
                             .findFirstAsync();

C#:

// In C# you use LINQ for normal queries but searching by primary key is a special optimal call.
// This saves the overhead of parsing a generic LINQ expression and will return null or one result.
var myPrimaryKey = "cd53e641-8c60-49c6-bd09-13e37a89d2de";
var specificPerson = realm.Find<Person>(myPrimaryKey);

Properties marked as primary keys are also implicitly added to the properties that Realm will index, meaning that these fetch operations will be very fast. That being said, in order for the fetch to succeed, the value you specify needs to match the primary key exactly (ie no partial strings, or different letter cases).

In the past, we would also recommend using this mechanism to offload Realm work to a background thread. You would make a copy of the primary key of the object on the main thread, pass it to the background one, and perform the fetch on the background to retrieve the object.

That being said, for Objective C & Swift, we recently introduced a new feature called thread references that automates the process to a much more substantial degree. While this method of passing primary keys between threads will still work, we recommend you adopt the new functionality moving forward on these platforms. This functionality will be coming to Java soon, but for now you can at least offload work from the main thread by adding the async suffix to your Java queries and writes.

What about updating objects?

One very powerful feature of primary keys in Realm is that it lets you update the properties of specific objects without having to spend the effort of fetching a copy of them from the database beforehand. And if the object you’re trying to update doesn’t exist, Realm will then intelligently create and insert the object from scratch for you.

There are 2 main ways of doing this: using separate instances of Realm objects, or straight up dictionary objects.

Updating an object via an object copy is very simple:

Objective-C:


MyPerson *person = [[MyPerson alloc] init];
person.personID = @"My-Primary-Key";
person.name = "Tom Anglade";

// Update `person` if it already exists, add it if not.
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
	[realm addOrUpdateObject:person];
}];

Swift:


let person = MyPerson()
person.personID = "My-Primary-Key"
person.name = "Tom Anglade"

// Update `person` if it already exists, add it if not.
let realm = try! Realm()
try! realm.write {
	realm.add(person, update: true)
}

Java:


/* Note the person here is detached from Realm which why it 
   can be used across threads. */
final MyPerson person = new MyPerson();
person.setPersonID("My-Primary-Key");
person.setName("Tom Anglade");

realm.executeTransactionAsync(new Realm.Transaction() {
  public void execute(Realm bgRealm) {
    bgRealm.copyToRealmOrUpdate(person);
  }
});

// The dictionary approach is not available in C#. Pass in an object to Add with the update flag
// Update `person` if it already exists, add it if not.
realm.Write(() =>
{
    var updatedFred = new Person { Name = "Freddie", PersonID=myPrimaryKey };
    realm.Add(updatedFred, update:true);
});

There’s one pitfall in which you need to be aware for this method: every property of the object needs to be properly set. Since ‘nil’ (or null in Java’s case) is still a valid value in Realm, if you leave a property empty, then the previous entry in the database will be deleted. This is done by design since it is completely reasonable you might want to delete data from a database in a specific update.

If you do want to perform partial updates of a Realm object, it is instead recommended that you use dictionaries in Objective C or Swift, and JSON in Java:

Objective-C:


NSMutableDictionary *person = [NSMutableDictionary dictionary];
person["personID"] = @"My-Primary-Key";
person["name"] = "Tom Anglade";

// Update `person` if it already exists, add it if not.
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
	[realm addOrUpdateObject:person];
}];

Swift:


var person = ["personID": "My-Primary-Key", "name": "Tom Anglade"]

// Update `person` if it already exists, add it if not.
let realm = try! Realm()
try! realm.write {
	realm.add(person, update: true)
}

Java:


realm.executeTransactionAsync(new Realm.Transaction() {
  public void execute(Realm bgRealm) {

    bgRealm.createOrUpdateObjectFromJson(MyPerson.class,
      "{\"name\":\"Tom Anglade\",\"personID\":\"My-Primary-Key\"}");

    // Or for added type safety and readability, using the Gson APIs, you can do this.
    String json = new Gson().toJson(new MyPerson("My-Primary-Key", "Tom Anglade"));
    bgRealm.createOrUpdateObjectFromJson(MyPerson.class, json);
    
    // createOrUpdateObjectFromJson takes String, InputStream, or JSONObject and will only update
    // fields put into the JSON string.  Ommitted fields will be left untouched.
  }
});

In this way, you are able to specify just the properties you want to change, leaving the others untouched.

This is especially useful for caching API responses that were downloaded as JSON. There are a variety of third party libraries available to convert JSON strings to dictionaries, and then these can be passed directly to Realm.

Please note that at present, setting a property with the exact same value as it had before will still get updated (triggering the usual transaction records and change notifications), so it’s generally recommended to try and filter your dictionaries so only the values that have changed are passed into Realm. There is an issue tracking improving this experience on the Realm Cocoa GitHub page.

What about auto-incrementing keys?

Auto-incrementing keys has been a much often requested feature, but at the moment, it’s not offered as a feature in Realm.

That being said, if it’s something you want, it’s very easy to implement yourself in Realm as it’s really only a matter of querying for the latest object in the database, and incrementing its key by 1.

But to consider this at a more basic level, you should definitely ask yourself if you app implementation actually needs incremented keys. If the ordering of your objects doesn’t matter (And even still it might be safer to use a separate property to track ordering instead of the primary key), then it would still be better practice to use a randomized string instead of an incrementing integer.

What about foreign keys?

Because we call this mechanism in Realm ‘primary keys’, we periodically get questions about whether that means Realm has foreign keys as well.

Since Realm isn’t a relational database, it doesn’t officially support the concept of foreign keys where the primary key value of one object is referenced by another. Instead, Realm offers the ability directly link objects as children of a parent object. For one-to-one relationships, the child object’s type can be used as a proprty of the parent object, and for one-to-many relationships, a Realm List object can be used to store multiple child objects of the same type, just like an array.

That being said, if your app maintains multiple Realm files, and you want to represent a relationship between two objects located in these wholly separate Realm files, directly linking Realm objects obviously won’t work. In those instances, it would be appropriate to adopt a foreign key type of model where an object in one Realm file stores a copy of the primary key of another object in the other Realm file.

Conclusion

Primary keys are a very powerful feature of Realm. They allow for the very granular fetching of specific objects in a database, as well as for providing a more intelligent mechanism for updating data in the database.

If you haven’t already, definitely check them out and let us know what you think!

Next Up: Learn more about fine-grained notifications in Realm

General link arrow white

Tim Oliver

Tim Oliver hails from Perth, Australia! He has been an iOS developer for 6 years, and recently joined Realm in March 2015. Tim has a cool app called iComics and he loves karaoke! He does, in fact, also sometimes have the problem of too many kangaroos in his backyard.

Eric Maxwell

Eric is an Product Engineer at Realm. He has spent over a decade architecting and developing software for various companies across different industries, including healthcare, insurance, library science, and private aviation. His current focus is training, mentoring, and mobile development. He has developed and taught courses on Java, Android and iOS. When he’s not working, he enjoys time with family, traveling and improv comedy.