mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-19 07:54:26 +01:00
Created Fetching and querying (markdown)
266
Fetching-and-querying.md
Normal file
266
Fetching-and-querying.md
Normal file
@@ -0,0 +1,266 @@
|
||||
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 `NSString`'s, `NSNumber`'s, `Int`'s, `NSDate`'s, an `NSDictionary` of key-values, etc.
|
||||
- only specific 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:
|
||||
```swift
|
||||
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, provide the `From` clause the configuration name for the destination persistent store:
|
||||
```swift
|
||||
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`:
|
||||
```swift
|
||||
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 `NSManagedObjectID`'s for all objects that match the criteria.
|
||||
- `fetchObjectID(_:_:)` - returns the `NSManagedObjectID`'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.
|
||||
|
||||
**`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):
|
||||
```swift
|
||||
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:
|
||||
```swift
|
||||
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 in the conditions:
|
||||
```swift
|
||||
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.
|
||||
```swift
|
||||
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`. The associated value for the `SortKey` enumeration is the attribute key string.
|
||||
|
||||
You can use the `+` and `+=` operator to append `OrderBy`'s together. This is useful when sorting conditionally:
|
||||
```swift
|
||||
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, well, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties:
|
||||
```swift
|
||||
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.
|
||||
|
||||
Do note that while `Tweak` lets you micro-configure its `NSFetchRequest`, don't forget 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 querying 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 and the return type:
|
||||
```swift
|
||||
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 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 omit the generic type:
|
||||
```swift
|
||||
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(...)`
|
||||
- `.Median(...)`
|
||||
- `.Minimum(...)`
|
||||
- `.StandardDeviation(...)`
|
||||
- `.Sum(...)`
|
||||
|
||||
```swift
|
||||
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`:
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("name", "age")
|
||||
)
|
||||
```
|
||||
`personJSON` will then have the value:
|
||||
```json
|
||||
[
|
||||
[
|
||||
"name": "John Smith",
|
||||
"age": 30
|
||||
],
|
||||
[
|
||||
"name": "Jane Doe",
|
||||
"age": 22
|
||||
]
|
||||
]
|
||||
```
|
||||
You can also include an aggregate as well:
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("name", .Count("friends"))
|
||||
)
|
||||
```
|
||||
which returns:
|
||||
```swift
|
||||
[
|
||||
[
|
||||
"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:
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("name", .Count("friends", As: "friendsCount"))
|
||||
)
|
||||
```
|
||||
which now returns:
|
||||
```swift
|
||||
[
|
||||
[
|
||||
"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 only useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value anyway.
|
||||
```swift
|
||||
let personJSON = CoreStore.queryAttributes(
|
||||
From(MyPersonEntity),
|
||||
Select("age", .Count("age", As: "count")),
|
||||
GroupBy("age")
|
||||
)
|
||||
```
|
||||
this returns dictionaries that shows the count for each `"age"`:
|
||||
```swift
|
||||
[
|
||||
[
|
||||
"age": 42,
|
||||
"count": 1
|
||||
],
|
||||
[
|
||||
"age": 22,
|
||||
"count": 1
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## Contents
|
||||
- [[Architecture]]
|
||||
- [[Setting up]]
|
||||
- [[Saving and processing transactions]]
|
||||
- [[Fetching and querying]]
|
||||
- [[Logging and error handling]]
|
||||
- [[Observing changes and notifications]]
|
||||
Reference in New Issue
Block a user