In order to apply more than one fetch conditions on each of a subset property, I think that I can still use Subquery
to do that. For my test example, friends
property is a collection and it can be used as the first parameter in a Subquery
. In this way, the predicate, the third parameter in the Subquery
, can apply more than one fetch conditions on each friend.
Nested Subquery
Fortunately, it is possible to build a nested
Subquery
expression. The result of a Subquery
is an array(may be empty). By using NSExpression
's function count
, the second or nested Subquery
can be placed in a Subquery
expression's third parameter predicate: Subquery(...).@count > 0
as an example.Thereafter, it comes my nested
Subquery
test:
TestSubquery.TestNestedSubquery(nameContains: "L", birthYearLowLimit: 2010, friendsLevel: 1)
and the result is as follows:
- Creating Person object and friends...
- Person - name: Bob, birthYear: 1997
- Friend - name: Tony, birthYear: 2010, friends: (none)
- Friend - name: Lisa, birthYear: 2012, friends: (none)
- Collection expression: {<MyPlayground_Sources.Person: 0x618000070700>}
- Collection expression result value: (
- "<MyPlayground_Sources.Person: 0x618000070700>"
- )
- Person - name: Bob, birthYear: 1997
- Predicate: SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0
- Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x618000070700>}, $x, SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0)
- collection: {<MyPlayground_Sources.Person: 0x618000070700>}
- variable: x
- predicate: SUBQUERY($x.friends, $y, $y.name CONTAINS "L" AND $y.birthYear > 2010).@count > 0
- Subquery expression result value: Optional(<__NSArrayM 0x60800005f8f0>(
- <MyPlayground_Sources.Person: 0x618000070700>
- )
- )
- Subquery expression value as [Person]: [<MyPlayground_Sources.Person: 0x618000070700>] as follows
- Person - name: Bob, birthYear: 1997
- Friend - name: Tony, birthYear: 2010, friends: (none)
- Friend - name: Lisa, birthYear: 2012, friends: (none)
It is so fun to test
Subquery
in Playground
!One-to-many Relationships to More Than One Levels
With above knowledge about
Subquery
expression, we may use powerful Subquery
expression to go to deep more than one level of one-to-many relationships. For example, we may query persons with constrains on the second level of friends
, i.e., friends
' friends
.
TestSubquery.TestNestedSubquery(aggregate: .none, nameContains: "a1", checkFriendsNull: true, friendsLevel: 2)
The above test applies aggregate operator
NONE
on the second level friends
property of person Bob, i.e., friends
' friends
. Here are printed out debug information, as-expected:- Creating Person object and friends...
- Person - name: Bob, birthYear: 1997
- Friend - name: Tony, birthYear: 2010
- Friend - name: Tony1, birthYear: 2012, friends: (none)
- Friend - name: Lisa1, birthYear: 2011, friends: (none)
- Friend - name: Lisa, birthYear: 2012, friends: (none)
- Collection expression: {<MyPlayground_Sources.Person: 0x60000007fa00>}
- Collection expression result value: (
- "<MyPlayground_Sources.Person: 0x60000007fa00>"
- )
- Person - name: Bob, birthYear: 1997
- Predicate: SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0
- Subquery Expression: SUBQUERY({<MyPlayground_Sources.Person: 0x60000007fa00>}, $x, SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0)
- collection: {<MyPlayground_Sources.Person: 0x60000007fa00>}
- variable: x
- predicate: SUBQUERY($x.friends, $y, $y.friends != nil AND (NOT ANY $y.friends.name CONTAINS "a1")).@count > 0
- Subquery expression result value: Optional(<__NSArrayM 0x618000059fb0>(
- )
- )
- Subquery expression value as [Person]: [] as follows
empty array because none of
friends
' friends
Lisa1 contains "a1".Updates
Important findings! Recently (2017.02.21) I found an issue or restriction with
Subquery
. All the objects in Subquery
have to be key-value-coding-compliant, that is, all the objects have to be based on NSObject
.In other words, object from none
NSObject
based class such as struct
would cause exception or crash when calling method expressionValue(with: context:)
. Subquery
would not be able to figure out any methods without implementing key-value-observation(KVO) protocol. I spent almost more than one day to figure this issue out.Resources
- Blog by Mattt Thompson: NSExpression - NSHipster
- My previous blog: Playground and Testing Subquery by NSExpression (2)