如何调用 GraphQL 终结点
数据墙DBW中的 GraphQL 终结点让你能够更精确地查询和修改数据。每个查询都会明确声明你需要哪些字段,并支持通过参数对结果进行筛选、排序和分页。
默认情况下,数据墙DBW 将 GraphQL 终结点托管在:
https://{base_url}/graphql通过配置公开的实体会自动包含在 GraphQL 架构中。例如,如果你配置了 books 和 authors 实体,它们都会作为根字段出现在架构中。
NOTE
若要浏览架构并自动补全字段,请使用任何现代 GraphQL 客户端或 IDE(例如 Apollo、Insomnia 或 Visual Studio Code 的 GraphQL 扩展)。
数据墙DBW 支持的关键字
| 概念 | GraphQL | 用途 |
|---|---|---|
| 投影 | items | 选择要返回的字段 |
| 筛选 | filter | 按条件限制行 |
| 排序 | orderBy | 定义排序顺序 |
| 页大小 | first | 限制每页项目数 |
| 延续 | after | 从上一页继续 |
基本结构
每个 GraphQL 查询都从一个表示实体的根字段开始。所有 GraphQL 请求都使用 POST 发送到 /graphql 终结点,并在 JSON 请求体中包含查询内容。
{
books {
items {
id
title
year
pages
}
}
}响应是一个与选择集形状一致的 JSON 对象。分页和错误详情仅在适用时出现。
NOTE
默认情况下,除非另有配置,否则 数据墙DBW 每次查询最多返回 100 个项目(runtime.pagination.default-page-size)。
HTTP
POST https://localhost:5001/graphql
Content-Type: application/json
{
"query": "{ books { items { id title year pages } } }"
}成功:
{
"data": {
"books": {
"items": [
{ "id": 1, "title": "Dune", "year": 1965, "pages": 412 },
{ "id": 2, "title": "Foundation", "year": 1951, "pages": 255 }
]
}
}
}带分页的成功响应:
{
"data": {
"books": {
"items": [
{ "id": 1, "title": "Dune", "year": 1965, "pages": 412 },
{ "id": 2, "title": "Foundation", "year": 1951, "pages": 255 }
],
"hasNextPage": true,
"endCursor": "eyJpZCI6Mn0="
}
}
}错误:
{
"errors": [
{
"message": "Could not find item with the given key.",
"locations": [{ "line": 1, "column": 3 }],
"path": ["book_by_pk"]
}
]
}cURL
curl -X POST "https://localhost:5001/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ books { items { id title year pages } } }"}'C#
以下模型类用于反序列化 数据墙DBW GraphQL 响应:
using System.Text.Json.Serialization;
public class GraphQLRequest
{
[JsonPropertyName("query")]
public string Query { get; set; } = string.Empty;
[JsonPropertyName("variables")]
public object? Variables { get; set; }
}
public class GraphQLResponse<T>
{
[JsonPropertyName("data")]
public T? Data { get; set; }
[JsonPropertyName("errors")]
public List<GraphQLError>? Errors { get; set; }
[JsonIgnore]
public bool IsSuccess => Errors is null || Errors.Count == 0;
}
public class GraphQLError
{
[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
[JsonPropertyName("path")]
public List<string>? Path { get; set; }
}
public class BooksResponse
{
[JsonPropertyName("books")]
public BooksResult? Books { get; set; }
}
public class BooksResult
{
[JsonPropertyName("items")]
public List<Book>? Items { get; set; }
[JsonPropertyName("hasNextPage")]
public bool HasNextPage { get; set; }
[JsonPropertyName("endCursor")]
public string? EndCursor { get; set; }
}
public class Book
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
[JsonPropertyName("year")]
public int? Year { get; set; }
[JsonPropertyName("pages")]
public int? Pages { get; set; }
}调用 API 并反序列化响应:
public async Task<List<Book>> GetBooksAsync()
{
var request = new GraphQLRequest
{
Query = "{ books { items { id title year pages } } }"
};
var response = await httpClient.PostAsJsonAsync("graphql", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<GraphQLResponse<BooksResponse>>();
if (result?.Errors?.Count > 0)
{
throw new Exception(result.Errors[0].Message);
}
return result?.Data?.Books?.Items ?? [];
}Python
以下数据类用于建模 数据墙DBW GraphQL 响应:
from dataclasses import dataclass, field
import requests
@dataclass
class Book:
id: int
title: str
year: int | None = None
pages: int | None = None
@dataclass
class GraphQLError:
message: str
path: list[str] | None = None
@dataclass
class BooksResult:
items: list[Book] = field(default_factory=list)
has_next_page: bool = False
end_cursor: str | None = None
@dataclass
class GraphQLResponse:
data: dict | None = None
errors: list[GraphQLError] | None = None
@property
def is_success(self) -> bool:
return self.errors is None or len(self.errors) == 0调用 API 并解析响应:
def get_books(base_url: str) -> list[Book]:
query = "{ books { items { id title year pages } } }"
response = requests.post(
f"{base_url}/graphql",
json={"query": query}
)
response.raise_for_status()
data = response.json()
if "errors" in data and data["errors"]:
raise Exception(data["errors"][0]["message"])
items = data.get("data", {}).get("books", {}).get("items", [])
return [Book(**item) for item in items]JavaScript
以下函数调用 GraphQL API:
async function getBooks(baseUrl) {
const query = "{ books { items { id title year pages } } }";
const response = await fetch(`${baseUrl}/graphql`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const result = await response.json();
if (result.errors?.length > 0) {
throw new Error(result.errors[0].message);
}
return result.data?.books?.items ?? [];
}使用示例:
const books = await getBooks("https://localhost:5001");
console.log(`Fetched ${books.length} books from the API.`);查询类型
每个实体都支持两种标准根查询:
| 查询 | 说明 |
|---|---|
entity_by_pk | 按主键返回一条记录 |
entities | 返回匹配筛选条件的记录列表 |
返回单条记录的示例:
{
book_by_pk(id: 1010) {
title
year
}
}返回多条记录的示例:
{
books {
items {
id
title
}
}
}筛选结果
使用 filter 参数限制返回哪些记录。
{
books(filter: { title: { contains: "Foundation" } }) {
items { id title }
}
}此查询返回所有标题包含 “Foundation” 的图书。
筛选可以结合逻辑运算符:
{
authors(filter: {
or: [
{ first_name: { eq: "Isaac" } }
{ last_name: { eq: "Asimov" } }
]
}) {
items { first_name last_name }
}
}支持的 eq、neq、lt、lte 和 isNull 等运算符,详见 GraphQL filter 参考页;该页面可在对应专题中查看。
排序结果
orderBy 参数用于定义记录的排序方式。
{
books(orderBy: { year: DESC, title: ASC }) {
items { id title year }
}
}这将返回按 year 降序、再按 title 排列的图书。
有关详细信息,请参阅 GraphQL orderBy 参考页;该页面可在对应专题中查看。
限制结果
first 参数限制单次请求返回的记录数。
{
books(first: 5) {
items { id title }
}
}这会返回前五本图书,默认按主键排序。你也可以使用 first: -1 请求配置的最大页大小。
更多信息请参阅 GraphQL first 参考页;该页面可在对应专题中查看。
继续获取结果
要获取下一页,请使用上一查询返回的游标配合 after 参数。
{
books(first: 5, after: "eyJpZCI6NX0=") {
items { id title }
}
}after 令牌标记上一页结束的位置。
有关详细信息,请参阅 GraphQL after 参考页;该页面可在对应专题中查看。
字段选择(投影)
在 GraphQL 中,你可以精确指定响应中出现哪些字段。没有像 SELECT * 那样的通配符。只请求你需要的字段。
{
books {
items { id title price }
}
}你还可以使用别名重命名响应中的字段:
{
books {
items {
bookTitle: title
cost: price
}
}
}详情请参阅字段投影参考页;该页面可在对应专题中查看。
修改数据
GraphQL mutation 允许你根据实体权限创建、更新和删除记录。
| Mutation | 操作 |
|---|---|
createEntity | 创建新项 |
updateEntity | 更新现有项 |
deleteEntity | 删除项 |
NOTE
_by_pk 后缀只适用于查询(例如 book_by_pk)。Mutation 名称不包含该后缀,请使用 updateBook 和 deleteBook,而不是 updateBook_by_pk 或 deleteBook_by_pk。
IMPORTANT
GraphQL mutation 需要活动的数据库连接池。如果你的连接字符串设置了 Pooling=False 或 MultipleActiveResultSets=False,mutation 会因错误 Implicit distributed transactions have not been enabled 而失败。请设置 Pooling=True 和 MultipleActiveResultSets=True(SQL Server),或为你的数据库提供程序设置等效选项。
TIP
对于通过 GraphQL 公开的存储过程,数据墙DBW 会在实体名称前加上 execute 前缀。例如,名为 GetBookById 的存储过程实体会在架构中变为 executeGetBookById。有关详细信息,请参阅 GraphQL 存储过程页面;该页面可在对应专题中查看。
创建新记录
使用 create mutation 添加新项。
HTTP
POST https://localhost:5001/graphql
Content-Type: application/json
{
"query": "mutation { createBook(item: { id: 2000, title: \"Leviathan Wakes\", year: 2011, pages: 577 }) { id title year pages } }"
}cURL
curl -X POST "https://localhost:5001/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { createBook(item: { id: 2000, title: \"Leviathan Wakes\", year: 2011, pages: 577 }) { id title year pages } }"}'C#
var request = new GraphQLRequest
{
Query = @"
mutation CreateBook($item: CreateBookInput!) {
createBook(item: $item) { id title year pages }
}",
Variables = new
{
item = new { id = 2000, title = "Leviathan Wakes", year = 2011, pages = 577 }
}
};
var response = await httpClient.PostAsJsonAsync("graphql", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<GraphQLResponse<CreateBookResponse>>();
if (result?.Errors?.Count > 0)
{
throw new Exception(result.Errors[0].Message);
}Python
query = """
mutation CreateBook($item: CreateBookInput!) {
createBook(item: $item) { id title year pages }
}
"""
variables = {
"item": {"id": 2000, "title": "Leviathan Wakes", "year": 2011, "pages": 577}
}
response = requests.post(
f"{base_url}/graphql",
json={"query": query, "variables": variables}
)
response.raise_for_status()
data = response.json()
if "errors" in data and data["errors"]:
raise Exception(data["errors"][0]["message"])JavaScript
const query = `
mutation CreateBook($item: CreateBookInput!) {
createBook(item: $item) { id title year pages }
}
`;
const variables = {
item: { id: 2000, title: "Leviathan Wakes", year: 2011, pages: 577 },
};
const response = await fetch(`${baseUrl}/graphql`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
});
const result = await response.json();
if (result.errors?.length > 0) {
throw new Error(result.errors[0].message);
}更新现有记录
使用 update mutation 修改现有项的指定字段。
HTTP
POST https://localhost:5001/graphql
Content-Type: application/json
{
"query": "mutation { updateBook(id: 2000, item: { title: \"Leviathan Wakes\", year: 2011, pages: 577 }) { id title year pages } }"
}cURL
curl -X POST "https://localhost:5001/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { updateBook(id: 2000, item: { title: \"Leviathan Wakes\", year: 2011, pages: 577 }) { id title year pages } }"}'C#
var request = new GraphQLRequest
{
Query = @"
mutation UpdateBook($id: Int!, $item: UpdateBookInput!) {
updateBook(id: $id, item: $item) { id title year pages }
}",
Variables = new
{
id = 2000,
item = new { title = "Leviathan Wakes", year = 2011, pages = 577 }
}
};
var response = await httpClient.PostAsJsonAsync("graphql", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<GraphQLResponse<UpdateBookResponse>>();
if (result?.Errors?.Count > 0)
{
throw new Exception(result.Errors[0].Message);
}Python
query = """
mutation UpdateBook($id: Int!, $item: UpdateBookInput!) {
updateBook(id: $id, item: $item) { id title year pages }
}
"""
variables = {
"id": 2000,
"item": {"title": "Leviathan Wakes", "year": 2011, "pages": 577}
}
response = requests.post(
f"{base_url}/graphql",
json={"query": query, "variables": variables}
)
response.raise_for_status()
data = response.json()
if "errors" in data and data["errors"]:
raise Exception(data["errors"][0]["message"])JavaScript
const query = `
mutation UpdateBook($id: Int!, $item: UpdateBookInput!) {
updateBook(id: $id, item: $item) { id title year pages }
}
`;
const variables = {
id: 2000,
item: { title: "Leviathan Wakes", year: 2011, pages: 577 },
};
const response = await fetch(`${baseUrl}/graphql`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
});
const result = await response.json();
if (result.errors?.length > 0) {
throw new Error(result.errors[0].message);
}删除记录
使用 delete mutation 按主键删除项。
HTTP
POST https://localhost:5001/graphql
Content-Type: application/json
{
"query": "mutation { deleteBook(id: 2000) { id title } }"
}cURL
curl -X POST "https://localhost:5001/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { deleteBook(id: 2000) { id title } }"}'C#
var request = new GraphQLRequest
{
Query = @"
mutation DeleteBook($id: Int!) {
deleteBook(id: $id) { id title }
}",
Variables = new { id = 2000 }
};
var response = await httpClient.PostAsJsonAsync("graphql", request);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<GraphQLResponse<DeleteBookResponse>>();
if (result?.Errors?.Count > 0)
{
throw new Exception(result.Errors[0].Message);
}Python
query = """
mutation DeleteBook($id: Int!) {
deleteBook(id: $id) { id title }
}
"""
variables = {"id": 2000}
response = requests.post(
f"{base_url}/graphql",
json={"query": query, "variables": variables}
)
response.raise_for_status()
data = response.json()
if "errors" in data and data["errors"]:
raise Exception(data["errors"][0]["message"])JavaScript
const query = `
mutation DeleteBook($id: Int!) {
deleteBook(id: $id) { id title }
}
`;
const variables = { id: 2000 };
const response = await fetch(`${baseUrl}/graphql`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
});
const result = await response.json();
if (result.errors?.length > 0) {
throw new Error(result.errors[0].message);
}