GraphQL 之 Filtering, Sorting, Pagination, Projections
GraphQL 的價值就在會依 Filtering, Sorting, Pagination, Projections 自動組織後端查詢程式碼。
引言
GraphQL 的價值就在會依 Filtering(過濾), Sorting(排序), Pagination(分頁), Projections(投影) 自動組織後端查詢程式碼。這幾項功能是不用再加碼就可以實現。
經過數次的迭代,就 Hot Chocolate 來說可以輕鬆的就實現,當然還是有些前題條件的。軟體規範設計的越玄實作就越難。GraphQL 的規範就很玄那是怎麼做到的,這應該歸功於 LINQ 的動態性,Hot Chocolate 就是基於 LINQ IQueryable 介面實現的。
這四個項目的實作都在後端,前端只要會下規範好的指令即可。
Projections(投影) / UseProjection
其中比較特別的是 Projections(投影)。Projections 的作用是優化後端查詢指令,沒有實作並不會影嚮邏輯上的結果。它優化的部份是去除查詢過程中自動排除不需要的欄位,減少不必要的資料流量。在組織 Projections 之前會取該 table 全部的欄位;Projections 之後只取該 table 需要的部份。
範例:若有一 GrqphQL query 我們只要3個欄位: id, name, city。如下:
query {
user {
id,
name,
addrsss {
city
}
}
}
在 Projections 之前,對映到後端的 DB Query 指令是
SELECT *
FROM user U
JOIN address A on U.id = A.userId
-- 若 user 有 100 個欄位 address 有 10 個欄位,在 Projections 之前查詢過程會取全部欄位。
-- 在此例就是取 110 個欄位。在最後的最後 GraphQL 送回只需要的 3 個欄位。
在 Projections 之後變成
SELECT U.id, U.name, A.city
FROM user U
JOIN address A ON U.id = A.userId
-- 在 Projections 後,查詢過程只會取必要的 3 個欄位。
Projection 實務上資料來源若是映射 DB → ORM → LINQ → GrqphQL ,那這個 Projections 作用就很明顯。而若是自訂的資料來源的話意義就不大。
直接映射資料庫
就 Hot Chocolate 來說有支援數種資料庫 Entiy Framework Core, MongoDB, Neo4J 等,只要組織好就能進行 GraphQL query 不必再寫 query 程式碼。
參考文件
各別的說明請直接參考官方說明。
Filtering / UseFiltering - GraphQL 規範
從練習的範例來看。進一步的再去查文件。
Graph Schema - about filter (後端)
# 本例混用 filtering, sorting
type Query {
bookList(where: BookFilterInput, order: [BookSortInput!]): [Book!]!
}
# 重點說明 filter 相關的 shema 內容。
input BookFilterInput {
and: [BookFilterInput!]
or: [BookFilterInput!]
title: StringOperationFilterInput
author: AuthorFilterInput
}
input StringOperationFilterInput {
and: [StringOperationFilterInput!]
or: [StringOperationFilterInput!]
eq: String
neq: String
contains: String
ncontains: String
in: [String]
nin: [String]
startsWith: String
nstartsWith: String
endsWith: String
nendsWith: String
}
input AuthorFilterInput {
and: [AuthorFilterInput!]
or: [AuthorFilterInput!]
name: StringOperationFilterInput
}
type Book {
title: String!
author: Author!
}
[..略...]
GraphQL query - about filter (前端)
# 本例 filtering
query GetBookListFilter {
bookList(
where: {
and:[
{ title: {contains: "Python"}},
{ author: { name: { eq: "莊泉福"}}}
]
}
) {
title,
author {
name
}
}
}
Sorting / UseSorting - GraphQL 規範
從練習的範例來看。進一步的再去查文件。
Graph Schema - about filter (後端)
type Query {
bookList(where: BookFilterInput, order: [BookSortInput!]): [Book!]!
}
input BookSortInput {
title: SortEnumType
author: AuthorSortInput
}
enum SortEnumType {
ASC
DESC
}
input AuthorSortInput {
name: SortEnumType
}
[..略...]
Graph query - about filter (前端)
# 本例混用 filtering, sorting
query GetBookListFilter {
bookList(
order: {
title: DESC
}
where: {
title: {contains: "Python"}
}
) {
title
}
}
Pagination / UsePaging - GraphQL 規範
從練習的範例來看。進一步的再去查文件。
Graph Schema - about filter (後端)
type Query {
booksPage(
first: Int # 下一頁參數,筆數
after: String # 下一頁參數,endCursor of this page.
last: Int # 上一頁參數,筆數
before: String # 上一頁參數,startCursor of this page.
): BooksPageConnection
}
"""
A connection to a list of items.
"""
type BooksPageConnection {
pageInfo: PageInfo!
edges: [BooksPageEdge!]
nodes: [Book!]
}
"""
Information about pagination in a connection.
"""
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
"""
An edge in a connection.
"""
type BooksPageEdge {
cursor: String!
node: Book!
}
Graph query - about filter (前端)
# 第一頁:取6筆
query GetBooksNextPage {
booksPage(
first:6,
after: null
) {
pageInfo {
hasNextPage,
hasPreviousPage,
startCursor,
endCursor
}
nodes {
title
}
}
}
# 下一頁:取6筆
query GetBooksNextPage {
booksPage(
first:6,
after: "NQ==" # 下一頁參數,endCursor of this page.
) {
pageInfo {
hasNextPage,
hasPreviousPage,
startCursor,
endCursor
}
nodes {
title
}
}
}
# 上一頁:取6筆
query GetBooksPrevPage {
booksPage(
last:6,
before: "Ng==" #參數,startCursor of this page.
) {
pageInfo {
hasNextPage,
hasPreviousPage,
startCursor,
endCursor
}
nodes {
title
}
}
}
實作環境
平台: .NET6 IDE: Visual Studio 2022 框架: Blazor Server App GraphQL 套件: Hot Chocolate v13.8.1
實作紀錄(關鍵程式碼)
實作上這四種可以同時混用,但需遵守順序。
實作 Filtering, Sorting, Pagination 都是後端的工作。經過數次的迭代,就 Hot Chocolate 來說可以輕鬆的就實現,只要掛上相應的 middleware / attribute 就完成了。
首先安裝套件 HotChocolate.Data
dotnet add package HotChocolate.Data ## ※HotChocolate.* 相關套件版本需一致。
在 Program.cs 註冊
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//## for GrqphQL
builder.Services
.AddGraphQLServer()
.AddQueryType<Query>()
.AddType<ProductQuery>()
.AddType<BookQuery>()
.AddMutationType<Mutation>()
.AddSubscriptionType<Subscription>() // for GrqphQL subscriptions.
.AddInMemorySubscriptions()
.AddProjections() //------ enable projection
.AddFiltering() //------ enable filter
.AddSorting(); //------ enable sorting
// Pagination 不用額外註冊
[...略...]
var app = builder.Build();
[...略...]
//## for GrqphQL
app.MapGraphQL();
app.UseWebSockets(); // for GrqphQL subscriptions.
app.Run();
加掛 Filtering, Sorting, Pagination, Projections
Note: If you use more than one middleware, keep in mind that ORDER MATTERS. The correct order is UsePaging > UseProjection > UseFiltering > UseSorting.
[ExtendObjectType(nameof(Query))]
public class BookQuery
{
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting] //---※這四個順序必需正確。
public IQueryable<Book> GetBookList() => _books.AsQueryable();
[UseFirstOrDefault] //---只傳回一筆,仍保有 filter/sorting 能力。
[UseFiltering]
[UseSorting] //---也可以只掛想要的功能即可。
public IQueryable<Book> GetBookList() => _books.AsQueryable();
[UsePaging] //---也可以只掛想要的功能即可。
public IQueryable<Book> GetBooksPage() => _books.AsQueryable();
[UseFiltering] //---也可以混合自訂的(條件)參數,注意名稱別衝突!
public IQueryable<Book> GetBookListX(string title) =>
_books.Where(c => c.Title.Contains(title)).AsQueryable();
[...略...]
}
完整程式碼
(EOF)
Last updated