Skip to content

Commit ac285b0

Browse files
authored
Merge pull request #1208 from json-api-dotnet/docs-queries-example
Updated example to match with current implementation
2 parents d6286c3 + 75fad29 commit ac285b0

File tree

1 file changed

+114
-54
lines changed

1 file changed

+114
-54
lines changed

docs/internals/queries.md

+114-54
Original file line numberDiff line numberDiff line change
@@ -22,66 +22,76 @@ Processing a request involves the following steps:
2222
- `JsonApiResourceService` contains no more usage of `IQueryable`.
2323
- `EntityFrameworkCoreRepository` delegates to `QueryableBuilder` to transform the `QueryLayer` tree into `IQueryable` expression trees.
2424
`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.
2626
- `JsonApiWriter` transforms resource objects into json response.
2727

2828
# Example
2929
To get a sense of what this all looks like, let's look at an example query string:
3030

3131
```
3232
/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)&
3636
page[number]=3&
3737
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
4444
```
4545

4646
After parsing, the set of scoped expressions is transformed into the following tree by `QueryLayerComposer`:
4747

4848
```
4949
QueryLayer<Blog>
5050
{
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)
5454
Pagination: Page number: 3, size: 5
55-
Projection
55+
Selection
5656
{
57-
title
58-
id
59-
owner: QueryLayer<Author>
57+
FieldSelectors<Blog>
6058
{
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>
7062
{
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
7467
{
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>
7969
{
80-
publishTime
70+
url
8171
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+
}
8289
}
8390
}
8491
}
92+
owner: QueryLayer<WebAccount>
93+
{
94+
}
8595
}
8696
}
8797
}
@@ -90,36 +100,86 @@ QueryLayer<Blog>
90100
Next, the repository translates this into a LINQ query that the following C# code would represent:
91101

92102
```c#
93-
var query = dbContext.Blogs
103+
IQueryable<Blog> query = dbContext.Blogs
104+
.Include("Posts.Comments.Author")
94105
.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)
98108
.Skip(10)
99109
.Take(5)
100110
.Select(blog => new Blog
101111
{
102112
Title = blog.Title,
103113
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)
108117
.Take(5)
109-
.Select(article => new Article
118+
.Select(blogPost => new BlogPost
110119
{
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)
117127
.Take(5)
118-
.Select(revision => new Revision
128+
.Select(comment => new Comment
119129
{
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
124136
});
125137
```
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

Comments
 (0)