Skip to content

Fetching and querying

John Estropia edited this page Jul 18, 2015 · 3 revisions

Before we dive in, be aware that CoreStore distinguishes between fetching and querying:

  • A fetch executes searches from a specific transaction or data stack. This means fetches can include pending objects (i.e. before a transaction calls on commit().) Use fetches when:
    • results need to be NSManagedObject instances
    • unsaved objects should be included in the search (though fetches can be configured to exclude unsaved ones)
  • A query pulls data straight from the persistent store. This means faster searches when computing aggregates such as count, min, max, etc. Use queries when:
    • you need to compute aggregate functions (see below for a list of supported functions)
    • results can be raw values like NSStrings, NSNumbers, Ints, NSDates, an NSDictionary of key-values, etc.
    • only values for specified attribute keys need to be included in the results
    • unsaved objects should be ignored

From clause

The search conditions for fetches and queries are specified using clauses. All fetches and queries require a From clause that indicates the target entity type:

let people = CoreStore.fetchAll(From(MyPersonEntity))
// CoreStore.fetchAll(From<MyPersonEntity>()) works as well

people in the example above will be of type [MyPersonEntity]. The From(MyPersonEntity) clause indicates a fetch to all persistent stores that MyPersonEntity belong to.

If the entity exists in multiple configurations and you need to only search from a particular configuration, indicate in the From clause the configuration name for the destination persistent store:

let people = CoreStore.fetchAll(From<MyPersonEntity>("Config1")) // ignore objects in persistent stores other than the "Config1" configuration

or if the persistent store is the auto-generated "Default" configuration, specify nil:

let person = CoreStore.fetchAll(From<MyPersonEntity>(nil))

Now we know how to use a From clause, let's move on to fetching and querying.

Fetching

There are currently 5 fetch methods you can call from CoreStore, from a DataStack instance, or from a BaseDataTransaction instance. All of the methods below accept the same parameters: a required From clause, and an optional series of Where, OrderBy, and/or Tweak clauses.

  • fetchAll(...) - returns an array of all objects that match the criteria.
  • fetchOne(...) - returns the first object that match the criteria.
  • fetchCount(...) - returns the number of objects that match the criteria.
  • fetchObjectIDs(...) - returns an array of NSManagedObjectIDs for all objects that match the criteria.
  • fetchObjectID(...) - returns the NSManagedObjectIDs for the first objects that match the criteria.

Each method's purpose is straightforward, but we need to understand how to set the clauses for the fetch.

Where clause

The Where clause is CoreStore's NSPredicate wrapper. It specifies the search filter to use when fetching (or querying). It implements all initializers that NSPredicate does (except for -predicateWithBlock:, which Core Data does not support):

var people = CoreStore.fetchAll(
    From(MyPersonEntity),
    Where("%K > %d", "age", 30) // string format initializer
)
people = CoreStore.fetchAll(
    From(MyPersonEntity),
    Where(true) // boolean initializer
)

If you do have an existing NSPredicate instance already, you can pass that to Where as well:

let predicate = NSPredicate(...)
var people = CoreStore.fetchAll(
    From(MyPersonEntity),
    Where(predicate) // predicate initializer
)

Where clauses also implement the &&, ||, and ! logic operators, so you can provide logical conditions without writing too much AND, OR, and NOT strings:

var people = CoreStore.fetchAll(
    From(MyPersonEntity),
    Where("age > %d", 30) && Where("gender == %@", "M")
)

If you do not provide a Where clause, all objects that belong to the specified From will be returned.

OrderBy clause

The OrderBy clause is CoreStore's NSSortDescriptor wrapper. Use it to specify attribute keys in which to sort the fetch (or query) results with.

var mostValuablePeople = CoreStore.fetchAll(
    From(MyPersonEntity),
    OrderBy(.Descending("rating"), .Ascending("surname"))
)

As seen above, OrderBy accepts a list of SortKey enumeration values, which can be either .Ascending or .Descending.

You can use the + and += operator to append OrderBys together. This is useful when sorting conditionally:

var orderBy = OrderBy(.Descending("rating"))
if sortFromYoungest {
    orderBy += OrderBy(.Ascending("age"))
}
var mostValuablePeople = CoreStore.fetchAll(
    From(MyPersonEntity),
    orderBy
)

Tweak clause

The Tweak clause lets you, uh, tweak the fetch (or query). Tweak exposes the NSFetchRequest in a closure where you can make changes to its properties:

var people = CoreStore.fetchAll(
    From(MyPersonEntity),
    Where("age > %d", 30),
    OrderBy(.Ascending("surname")),
    Tweak { (fetchRequest) -> Void in
        fetchRequest.includesPendingChanges = false
        fetchRequest.returnsObjectsAsFaults = false
        fetchRequest.includesSubentities = false
    }
)

The clauses are evaluated the order they appear in the fetch/query, so you typically need to set Tweak as the last clause. Tweak's closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions.

While Tweak lets you micro-configure the NSFetchRequest, note that CoreStore already preconfigured that NSFetchRequest to suitable defaults. Only use Tweak when you know what you are doing!

Querying

One of the functionalities overlooked by other Core Data wrapper libraries is raw properties fetching. If you are familiar with NSDictionaryResultType and -[NSFetchedRequest propertiesToFetch], you probably know how painful it is to setup a query for raw values and aggregate values. CoreStore makes this easy by exposing the 2 methods below:

  • queryValue(...) - returns a single raw value for an attribute or for an aggregate value. If there are multiple results, queryValue(...) only returns the first item.
  • queryAttributes(...) - returns an array of dictionaries containing attribute keys with their corresponding values.

Both methods above accept the same parameters: a required From clause, a required Select<T> clause, and an optional series of Where, OrderBy, GroupBy, and/or Tweak clauses.

Setting up the From, Where, OrderBy, and Tweak clauses is similar to how you would when fetching. For querying, you also need to know how to use the Select<T> and GroupBy clauses.

Select<T> clause

The Select<T> clause specifies the target attribute/aggregate key, as well as the expected return type:

let johnsAge = CoreStore.queryValue(
    From(MyPersonEntity),
    Select<Int>("age"),
    Where("name == %@", "John Smith")
)

The example above queries the "age" property for the first object that matches the Where condition. johnsAge will be bound to type Int?, as indicated by the Select<Int> generic type. For queryValue(...), the following are allowed as the return type (and therefore as the generic type for Select<T>):

  • Bool
  • Int8
  • Int16
  • Int32
  • Int64
  • Double
  • Float
  • String
  • NSNumber
  • NSString
  • NSDecimalNumber
  • NSDate
  • NSData
  • NSManagedObjectID
  • NSString

For queryAttributes(...), only NSDictionary is valid for Select, thus you are allowed to omit the generic type:

let allAges = CoreStore.queryAttributes(
    From(MyPersonEntity),
    Select("age")
)

If you only need a value for a particular attribute, you can just specify the key name (like we did with Select<Int>("age")), but several aggregate functions can also be used as parameter to Select:

  • .Average(...)
  • .Count(...)
  • .Maximum(...)
  • .Minimum(...)
  • .Sum(...)
let oldestAge = CoreStore.queryValue(
    From(MyPersonEntity),
    Select<Int>(.Maximum("age"))
)

For queryAttributes(...) which returns an array of dictionaries, you can specify multiple attributes/aggregates to Select:

let personJSON = CoreStore.queryAttributes(
    From(MyPersonEntity),
    Select("name", "age")
)

personJSON will then have the value:

[
    [
        "name": "John Smith",
        "age": 30
    ],
    [
        "name": "Jane Doe",
        "age": 22
    ]
]

You can also include an aggregate as well:

let personJSON = CoreStore.queryAttributes(
    From(MyPersonEntity),
    Select("name", .Count("friends"))
)

which returns:

[
    [
        "name": "John Smith",
        "count(friends)": 42
    ],
    [
        "name": "Jane Doe",
        "count(friends)": 231
    ]
]

The "count(friends)" key name was automatically used by CoreStore, but you can specify your own key alias if you need:

let personJSON = CoreStore.queryAttributes(
    From(MyPersonEntity),
    Select("name", .Count("friends", As: "friendsCount"))
)

which now returns:

[
    [
        "name": "John Smith",
        "friendsCount": 42
    ],
    [
        "name": "Jane Doe",
        "friendsCount": 231
    ]
]

GroupBy clause

The GroupBy clause lets you group results by a specified attribute/aggregate. This is useful only for queryAttributes(...) since queryValue(...) just returns the first value.

let personJSON = CoreStore.queryAttributes(
    From(MyPersonEntity),
    Select("age", .Count("age", As: "count")),
    GroupBy("age")
)

this returns dictionaries that shows the count for each "age":

[
    [
        "age": 42,
        "count": 1
    ],
    [
        "age": 22,
        "count": 1
    ]
]

Contents