Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Lines changed: 114 additions & 54 deletions
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月01日Z") &&
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)

AltStyle によって変換されたページ (->オリジナル) /