@@ -22,66 +22,76 @@ Processing a request involves the following steps:
22
22
- `JsonApiResourceService` contains no more usage of `IQueryable`.
23
23
- ` EntityFrameworkCoreRepository ` delegates to ` QueryableBuilder ` to transform the ` QueryLayer ` tree into ` IQueryable ` expression trees.
24
24
`QueryBuilder` depends on `QueryClauseBuilder` implementations that visit the tree nodes, transforming them to `System.Linq.Expression` equivalents.
25
- The `IQueryable` expression trees are executed by Entity Framework Core, which produces SQL statements out of them.
25
+ The `IQueryable` expression trees are passed to Entity Framework Core, which produces SQL statements out of them.
26
26
- ` JsonApiWriter ` transforms resource objects into json response.
27
27
28
28
# Example
29
29
To get a sense of what this all looks like, let's look at an example query string:
30
30
31
31
```
32
32
/api/v1/blogs?
33
- include=owner,articles.revisions .author&
34
- filter=has(articles )&
35
- sort=count(articles )&
33
+ include=owner,posts.comments .author&
34
+ filter=has(posts )&
35
+ sort=count(posts )&
36
36
page[number]=3&
37
37
fields[blogs]=title&
38
- filter[articles ]=and(not(equals(author.firstName ,null)),has(revisions ))&
39
- sort[articles ]=author.lastName &
40
- fields[articles ]=url&
41
- filter[articles.revisions ]=and(greaterThan(publishTime ,'2001-01-01 '),startsWith(author.firstName ,'J'))&
42
- sort[articles.revisions ]=-publishTime ,author.lastName &
43
- fields[revisions]=publishTime
38
+ filter[posts ]=and(not(equals(author.userName ,null)),has(comments ))&
39
+ sort[posts ]=author.displayName &
40
+ fields[blogPosts ]=url&
41
+ filter[posts.comments ]=and(greaterThan(createdAt ,'2001-01-01Z '),startsWith(author.userName ,'J'))&
42
+ sort[posts.comments ]=-createdAt ,author.displayName &
43
+ fields[comments]=createdAt
44
44
```
45
45
46
46
After parsing, the set of scoped expressions is transformed into the following tree by ` QueryLayerComposer ` :
47
47
48
48
```
49
49
QueryLayer<Blog>
50
50
{
51
- Include: owner,articles.revisions
52
- Filter: has(articles )
53
- Sort: count(articles )
51
+ Include: owner,posts.comments.author
52
+ Filter: has(posts )
53
+ Sort: count(posts )
54
54
Pagination: Page number: 3, size: 5
55
- Projection
55
+ Selection
56
56
{
57
- title
58
- id
59
- owner: QueryLayer<Author>
57
+ FieldSelectors<Blog>
60
58
{
61
- Sort: id
62
- Pagination: Page number: 1, size: 5
63
- }
64
- articles: QueryLayer<Article>
65
- {
66
- Filter: and(not(equals(author.firstName,null)),has(revisions))
67
- Sort: author.lastName
68
- Pagination: Page number: 1, size: 5
69
- Projection
59
+ title
60
+ id
61
+ posts: QueryLayer<BlogPost>
70
62
{
71
- url
72
- id
73
- revisions: QueryLayer<Revision>
63
+ Filter: and(not(equals(author.userName,null)),has(comments))
64
+ Sort: author.displayName
65
+ Pagination: Page number: 1, size: 5
66
+ Selection
74
67
{
75
- Filter: and(greaterThan(publishTime,'2001-01-01'),startsWith(author.firstName,'J'))
76
- Sort: -publishTime,author.lastName
77
- Pagination: Page number: 1, size: 5
78
- Projection
68
+ FieldSelectors<BlogPost>
79
69
{
80
- publishTime
70
+ url
81
71
id
72
+ comments: QueryLayer<Comment>
73
+ {
74
+ Filter: and(greaterThan(createdAt,'2001-01-01'),startsWith(author.userName,'J'))
75
+ Sort: -createdAt,author.displayName
76
+ Pagination: Page number: 1, size: 5
77
+ Selection
78
+ {
79
+ FieldSelectors<Comment>
80
+ {
81
+ createdAt
82
+ id
83
+ author: QueryLayer<WebAccount>
84
+ {
85
+ }
86
+ }
87
+ }
88
+ }
82
89
}
83
90
}
84
91
}
92
+ owner: QueryLayer<WebAccount>
93
+ {
94
+ }
85
95
}
86
96
}
87
97
}
@@ -90,36 +100,86 @@ QueryLayer<Blog>
90
100
Next, the repository translates this into a LINQ query that the following C# code would represent:
91
101
92
102
``` c#
93
- var query = dbContext .Blogs
103
+ IQueryable < Blog > query = dbContext .Blogs
104
+ .Include (" Posts.Comments.Author" )
94
105
.Include (" Owner" )
95
- .Include (" Articles.Revisions" )
96
- .Where (blog => blog .Articles .Any ())
97
- .OrderBy (blog => blog .Articles .Count )
106
+ .Where (blog => blog .Posts .Any ())
107
+ .OrderBy (blog => blog .Posts .Count )
98
108
.Skip (10 )
99
109
.Take (5 )
100
110
.Select (blog => new Blog
101
111
{
102
112
Title = blog .Title ,
103
113
Id = blog .Id ,
104
- Owner = blog .Owner ,
105
- Articles = new List <Article >(blog .Articles
106
- .Where (article => article .Author .FirstName != null && article .Revisions .Any ())
107
- .OrderBy (article => article .Author .LastName )
114
+ Posts = blog .Posts
115
+ .Where (blogPost => blogPost .Author .UserName != null && blogPost .Comments .Any ())
116
+ .OrderBy (blogPost => blogPost .Author .DisplayName )
108
117
.Take (5 )
109
- .Select (article => new Article
118
+ .Select (blogPost => new BlogPost
110
119
{
111
- Url = article .Url ,
112
- Id = article .Id ,
113
- Revisions = new HashSet <Revision >(article .Revisions
114
- .Where (revision => revision .PublishTime > DateTime .Parse (" 2001-01-01" ) && revision .Author .FirstName .StartsWith (" J" ))
115
- .OrderByDescending (revision => revision .PublishTime )
116
- .ThenBy (revision => revision .Author .LastName )
120
+ Url = blogPost .Url ,
121
+ Id = blogPost .Id ,
122
+ Comments = blogPost .Comments
123
+ .Where (comment => comment .CreatedAt > DateTime .Parse (" 2001-01-01Z" ) &&
124
+ comment .Author .UserName .StartsWith (" J" ))
125
+ .OrderByDescending (comment => comment .CreatedAt )
126
+ .ThenBy (comment => comment .Author .DisplayName )
117
127
.Take (5 )
118
- .Select (revision => new Revision
128
+ .Select (comment => new Comment
119
129
{
120
- PublishTime = revision .PublishTime ,
121
- Id = revision .Id
122
- }))
123
- }))
130
+ CreatedAt = comment .CreatedAt ,
131
+ Id = comment .Id ,
132
+ Author = comment .Author
133
+ }).ToHashSet ()
134
+ }).ToList (),
135
+ Owner = blog .Owner
124
136
});
125
137
```
138
+
139
+ The LINQ query gets translated by Entity Framework Core into the following SQL:
140
+
141
+ ``` sql
142
+ SELECT t." Title" , t." Id" , a." Id" , t2." Url" , t2." Id" , t2." Id0" , t2." CreatedAt" , t2." Id1" , t2." Id00" , t2." DateOfBirth" , t2." DisplayName" , t2." EmailAddress" , t2." Password" , t2." PersonId" , t2." PreferencesId" , t2." UserName" , a." DateOfBirth" , a." DisplayName" , a." EmailAddress" , a." Password" , a." PersonId" , a." PreferencesId" , a." UserName"
143
+ FROM (
144
+ SELECT b." Id" , b." OwnerId" , b." Title" , (
145
+ SELECT COUNT (* )::INT
146
+ FROM " Posts" AS p0
147
+ WHERE b." Id" = p0." ParentId" ) AS c
148
+ FROM " Blogs" AS b
149
+ WHERE EXISTS (
150
+ SELECT 1
151
+ FROM " Posts" AS p
152
+ WHERE b." Id" = p." ParentId" )
153
+ ORDER BY (
154
+ SELECT COUNT (* )::INT
155
+ FROM " Posts" AS p0
156
+ WHERE b." Id" = p0." ParentId" )
157
+ LIMIT @__Create_Item1_1 OFFSET @__Create_Item1_0
158
+ ) AS t
159
+ LEFT JOIN " Accounts" AS a ON t." OwnerId" = a." Id"
160
+ LEFT JOIN LATERAL (
161
+ SELECT t0." Url" , t0." Id" , t0." Id0" , t1." CreatedAt" , t1." Id" AS " Id1" , t1." Id0" AS " Id00" , t1." DateOfBirth" , t1." DisplayName" , t1." EmailAddress" , t1." Password" , t1." PersonId" , t1." PreferencesId" , t1." UserName" , t0." DisplayName" AS " DisplayName0" , t1." ParentId"
162
+ FROM (
163
+ SELECT p1." Url" , p1." Id" , a0." Id" AS " Id0" , a0." DisplayName"
164
+ FROM " Posts" AS p1
165
+ LEFT JOIN " Accounts" AS a0 ON p1." AuthorId" = a0." Id"
166
+ WHERE (t." Id" = p1." ParentId" ) AND (((a0." UserName" IS NOT NULL )) AND EXISTS (
167
+ SELECT 1
168
+ FROM " Comments" AS c
169
+ WHERE p1." Id" = c." ParentId" ))
170
+ ORDER BY a0." DisplayName"
171
+ LIMIT @__Create_Item1_1
172
+ ) AS t0
173
+ LEFT JOIN (
174
+ SELECT t3." CreatedAt" , t3." Id" , t3." Id0" , t3." DateOfBirth" , t3." DisplayName" , t3." EmailAddress" , t3." Password" , t3." PersonId" , t3." PreferencesId" , t3." UserName" , t3." ParentId"
175
+ FROM (
176
+ SELECT c0." CreatedAt" , c0." Id" , a1." Id" AS " Id0" , a1." DateOfBirth" , a1." DisplayName" , a1." EmailAddress" , a1." Password" , a1." PersonId" , a1." PreferencesId" , a1." UserName" , c0." ParentId" , ROW_NUMBER() OVER(PARTITION BY c0." ParentId" ORDER BY c0." CreatedAt" DESC , a1." DisplayName" ) AS row
177
+ FROM " Comments" AS c0
178
+ LEFT JOIN " Accounts" AS a1 ON c0." AuthorId" = a1." Id"
179
+ WHERE (c0." CreatedAt" > @__Create_Item1_2) AND ((@__Create_Item1_3 = ' ' ) OR (((a1." UserName" IS NOT NULL )) AND ((a1." UserName" LIKE @__Create_Item1_3 || ' %' ESCAPE ' ' ) AND (left(a1." UserName" , length(@__Create_Item1_3))::text = @__Create_Item1_3::text ))))
180
+ ) AS t3
181
+ WHERE t3 .row <= @__Create_Item1_1
182
+ ) AS t1 ON t0." Id" = t1." ParentId"
183
+ ) AS t2 ON TRUE
184
+ ORDER BY t .c , t." Id" , a." Id" , t2." DisplayName0" , t2." Id" , t2." Id0" , t2." ParentId" , t2." CreatedAt" DESC , t2." DisplayName" , t2." Id1"
185
+ ```
0 commit comments