Конечные точки GraphQL в построителе API данных (DAB) позволяют запрашивать и изменять данные с точностью.
Каждый запрос указывает, какие именно поля нужны, и поддерживает аргументы для фильтрации, упорядочивания и постраничной навигации.
По умолчанию DAB размещает конечную точку GraphQL по адресу:
https://{base_url}/graphql
Сущности, предоставляемые с помощью конфигурации, автоматически включаются в схему GraphQL.
Например, если у вас есть books и authors сущности, оба отображаются в качестве корневых полей в схеме.
Замечание
Чтобы изучить схему и поля автодополнения, используйте любой современный клиент GraphQL или интегрированную среду разработки (например, Apollo, Insomnia или расширение GraphQL для Visual Studio Code).
Ключевые слова, поддерживаемые в построителе API данных
| Концепция |
GraphQL |
Purpose |
| Проекция |
items |
Выберите возвращаемые поля |
| Фильтрация |
фильтр |
Ограничение строк по условию |
| Сортировка |
orderBy |
Определение порядка сортировки |
| Размер страницы |
первый |
Ограничение элементов на страницу |
| Продолжение |
после |
Продолжить с последней страницы |
Базовая структура
Каждый запрос GraphQL начинается с корневого поля, представляющего сущность.
Все запросы GraphQL используют POST в конечной точке /graphql с JSON-телом, содержащим запрос.
{
books {
items {
id
title
year
pages
}
}
}
Ответ — это объект JSON с той же фигурой, что и набор выбора.
Сведения о разбиении на страницы и сведения об ошибках отображаются только в том случае, если это применимо.
Замечание
По умолчанию DAB возвращает до 100 элементов на запрос, если не настроено в противном случае (runtime.pagination.default-page-size).
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 -X POST "https://localhost:5001/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ books { items { id title year pages } } }"}'
Следующие классы моделей десериализируют ответы DAB 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 ?? [];
}
Ответы DAB 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]
Следующая функция вызывает API GraphQL:
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и lteisNull.
Сортировка результатов
Аргумент orderBy определяет порядок сортировки записей.
{
books(orderBy: { year: DESC, title: ASC }) {
items { id title year }
}
}
Возвращаются книги, упорядоченные по year по убыванию, а затем по title.
Дополнительные сведения см. в справочнике по аргументу orderBy.
Ограничение результатов
Аргумент first ограничивает количество записей, возвращаемых в одном запросе.
{
books(first: 5) {
items { id title }
}
}
Это возвращает первые пять книг, упорядоченных по первичному ключу по умолчанию.
Вы также можете использовать first: -1 для запроса максимального размера страницы, установленного в настройках.
Дополнительные сведения см. в первом справочнике по аргументам.
Продолжение результатов
Чтобы получить следующую страницу, используйте after аргумент с курсором из предыдущего запроса.
{
books(first: 5, after: "eyJpZCI6NX0=") {
items { id title }
}
}
Маркер after помечает место окончания предыдущей страницы.
Дополнительные сведения см. в справочнике по аргументам after.
Выбор поля (проекция)
В GraphQL вы выбираете точно, какие поля отображаются в ответе.
Нет подстановочного знака, как SELECT *. Запросить только то, что вам нужно.
{
books {
items { id title price }
}
}
Вы также можете использовать псевдонимы для переименования полей в ответе:
{
books {
items {
bookTitle: title
cost: price
}
}
}
См. справочник по проекции полей для получения подробной информации.
Изменение данных
Изменения GraphQL позволяют создавать, обновлять и удалять записи в зависимости от разрешений сущности.
| Мутации |
Действие |
createEntity |
Создание нового элемента |
updateEntity |
Обновление существующего элемента |
deleteEntity |
Удалить элемент |
Замечание
Суффикс _by_pk применяется только к запросам (например, book_by_pk). Имена мутаций не включают этот суффикс — используйте updateBook и deleteBook, а не updateBook_by_pk или deleteBook_by_pk.
Это важно
Для мутаций GraphQL требуется активный пул подключений к базе данных. Если строка подключения устанавливает Pooling=False или MultipleActiveResultSets=False, изменения завершаются неудачей с ошибкой Implicit distributed transactions have not been enabled. Задайте Pooling=True и MultipleActiveResultSets=True (SQL Server) или эквивалент для поставщика базы данных.
Подсказка
Для хранимых процедур, предоставляемых с помощью GraphQL, DAB префиксирует имя сущности с execute. Например, сущность хранимой процедуры с именем GetBookById становится executeGetBookById в схеме. Дополнительные сведения см. в разделе хранимых процедур.
Создание новой записи
Используйте мутацию create для добавления нового элемента.
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 -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 } }"}'
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);
}
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"])
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 для изменения определенных полей в существующем элементе.
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 -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 } }"}'
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);
}
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"])
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 для удаления элемента по первичному ключу.
POST https://localhost:5001/graphql
Content-Type: application/json
{
"query": "mutation { deleteBook(id: 2000) { id title } }"
}
curl -X POST "https://localhost:5001/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { deleteBook(id: 2000) { id title } }"}'
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);
}
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"])
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);
}