-
-
Notifications
You must be signed in to change notification settings - Fork 255
Fetching and querying
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)
- results need to be
- 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
NSString
s,NSNumber
s,Int
s,NSDate
s, anNSDictionary
of key-values, etc. - only values for specified attribute keys need to be included in the results
- unsaved objects should be ignored
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.
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 ofNSManagedObjectID
s for all objects that match the criteria. -
fetchObjectID(...)
- returns theNSManagedObjectID
s 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.
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.
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 OrderBy
s 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
)
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!
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.
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
]
]
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
]
]