Skip to content

Query Generations

This year Apple released some new features in Core Data. One I am going to take a look at is Query Generations.

A Core Data Query Generation is a stable view of your data, or in other words, a snapshot in time. Also defined as a read transaction. All reads in a context will see the same view of data until you choose to advance it.

In this example I’ll show how a context can be “pinned” to a specific query generation.

Given a context we can pin it to the current query generation like so:

Once a context is pinned to a specific query generation it will be given a sable view of its data regardless of the SQLite store activity.

The initial state of the SQLite database can be seen in the image below:

SQLite store initial state



At a high level the steps I follow in the code below can be outlined like so:

1. have two contexts ready to be used, contextA and contextB

2. contextA -> set query generation to current

3. contextA -> we will fetch a specific person (dave0)

3. contextB -> update the name property of all persons to “DaveUpdate” in the SQLite database

4. contextA -> fetch dave23 and make sure the result is not nil, if not nil then test is good and we know name dave23 was not changed to “DaveUpdate” with the NSBatchUpdateRequest

5. contextA -> save and check queryGenerationToken, it must be nil right after save. This demonstrates how a pinned context will move to the store’s version at save time, at that point it can be re-pinned if needed

Here’s the code:

So, how does the code above prove that query generation is indeed giving us a snapshot in time of the data?

The result is not nil! It contains a Person entity even if contextB did change person.name for all Person entities in store. Here’s a screenshot of the state of the database right after the line (which will cause each person to have name == “DaveUpdate”):

After name Batch Update


Without pinning the context “let result = try fr23.execute()” would return no result because it would have seen persons with name == “DaveUpdate”.

What about a relationship?

Our model defines the following relationship: Person <->> Car. Every time we run this test the store is populated with Person entities, each Person entity is assigned one Car.
In contextB we update all person’s name values to “DaveUpdate”. In contextB we also delete the Car assigned to dave0.

Because contextA was pinned before contextB changed the content of the store, we’re then able to still fetch dave23, dave0 and its car even though the actual data in the SQLite store does not contain any person with name “dave23” and “dave0” and its cars relationship.

Conclusion:

The new Query Generation feature allows us to have the option of having a more transactional read approach within a certain context. It can be useful when the state needs to support a view maybe that simply needs to represent the state of the data at certain given point in time regardless of what happens to the actual data in the SQLite store concurrently. It’s also going to prevent the very annoying error we often get when dealing with faults that can’t be loaded from store anymore because of the stable state we have in a pinned context.

It is important to note that context registered objects are not automatically refreshed on generation update. If you need to do so you can call contextA.fetch() or contextA.refreshAllObjects().

In order to use query generations in Core Data you must use an SQLite store in WAL mode.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *