Введение

В этой статье мы поговорим о пагинации на GraphQL.

Одним из наиболее эффективных способов работы с GraphQL является взаимодействие при помощи интерфейса IQueryable. Наш второй проект в репозитории (002_GraphQLWithEFCore) посвящен обеспечению взаимодействия GraphQL с EF Core (Entity Framework (EF) Core — это простая, кроссплатформенная и расширяемая версия популярной технологии доступа к данным Entity Framework с открытым исходным кодом).

Сначала давайте введем данные о Customer (клиент) и Card (карта) в базу данных с помощью EF Core.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public List<Card> Cards { get; set; }
}

public class Card
{
    public int Id { get; set; }
    public string Number { get; set; }
    public string CVC { get; set; }
    public string ExpiryDate { get; set; }
    public int CustomerId { get; set; }
    public Customer Customer { get; set; }
}

Давайте настроим DbContext:

public class GraphQLAppDbContext : DbContext
{
    public GraphQLAppDbContext(DbContextOptions<GraphQLAppDbContext> options) : base(options) { }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<Card> Cards { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>()
            .HasMany(e => e.Cards)
            .WithOne(x => x.Customer)
            .HasForeignKey(x => x.CustomerId)
            .IsRequired();

        modelBuilder.Entity<Customer>()
            .HasData(new Customer() { Id = 1, Email = "id1@gmail.com", Name = "Simon Baker" },
                     new Customer() { Id = 2, Email = "id2@gmail.com", Name = "Hanayama Kaoru" },
                     new Customer() { Id = 3, Email = "id3@gmail.com", Name = "Hanma Baki" },
                     new Customer() { Id = 4, Email = "id4@gmail.com", Name = "O. Doppo" });

        modelBuilder.Entity<Card>()
            .HasData(new Card() { Id = 1, CustomerId = 1, Number = "1234-2345-3445-3456", CVC = "345", ExpiryDate = "08/26" },
                     new Card() { Id = 2, CustomerId = 2, Number = "1234-2345-3445-3456", CVC = "345", ExpiryDate = "08/26" },
                     new Card() { Id = 3, CustomerId = 3, Number = "3344-7654-3445-3456", CVC = "123", ExpiryDate = "09/25" },
                     new Card() { Id = 4, CustomerId = 4, Number = "5566-5454-3445-3456", CVC = "234", ExpiryDate = "07/24" },
                     new Card() { Id = 5, CustomerId = 1, Number = "6677-3232-3445-3456", CVC = "987", ExpiryDate = "06/26" },
                     new Card() { Id = 6, CustomerId = 1, Number = "8899-9898-3445-3456", CVC = "654", ExpiryDate = "05/27" },
                     new Card() { Id = 7, CustomerId = 2, Number = "9900-5678-3445-3456", CVC = "432", ExpiryDate = "04/28" },
                     new Card() { Id = 8, CustomerId = 1, Number = "1122-5678-3445-3456", CVC = "165", ExpiryDate = "03/23" },
                     new Card() { Id = 9, CustomerId = 2, Number = "2233-2543-3445-3456", CVC = "651", ExpiryDate = "02/29" },
                     new Card() { Id = 10, CustomerId = 3, Number = "3544-1234-3445-3456", CVC = "987", ExpiryDate = "01/26" });

        base.OnModelCreating(modelBuilder);
    }
}

Далее давайте настроим наш Query resolver (распознаватель запросов). (код)

public class Query
{
    public IQueryable<Customer> GetCustomers([Service] GraphQLAppDbContext graphqldbcontext)
    => graphqldbcontext.Customers.Include("Cards");
}

Наконец, давайте настроим hotchocolate, как в первом проекте.

using _002_GraphQLWithEFCore.Database;
using _002_GraphQLWithEFCore.Queries;

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<GraphQLAppDbContext>(
        options => options.UseSqlServer("Data Source=.;Initial Catalog=GraphQLDb;Integrated Security=SSPI;TrustServerCertificate=True;"));

builder.Services.AddGraphQLServer()
    .AddQueryType<Query>();

var app = builder.Build();

app.MapGraphQL();

app.Run();

Теперь, когда мы готовы запустить наш проект, можно получить всю информацию о клиенте и его карте в следующей форме.

Пагинация в GraphQL

Пагинация — это один из основных механизмов, который мы постоянно применяем на практике, и он позволяет получать данные по частям, а не целиком.

Используемые в настоящее время формы пагинации делятся на 2 вида:

Пагинация на основе смещения

Это форма пагинации с комбинацией Skip-Take, которую мы также часто используем в EF Core. Но в системах с большим потоком данных такая форма пагинации не очень удобна. Представьте, что вы получаете топ-10 данных в системе с огромным потоком информации; в систему добавляется все больше данных, следующие же 10 данных будут начальными 10-ю данными, которые вы уже получали. (мы не привязаны к элементам).

Допустим, skip(3).Take(3) вернет элементы [ 8, 7, 6 ], если нет уникального идентификатора сортировки. Но в следующий раз (сценарий 2), если при вызове пагинации информация уже была введена, мы снова получим те же данные, используя Skip(3).Take(3). ( [ 9, 8, 7] )

Пагинация на основе курсора

В отличие от пагинации на основе смещения, поскольку эта форма пагинации зависит от конкретных атрибутов сущности (в большинстве случаев от id), она всегда предоставляет уникальную информацию и по умолчанию перемещается вперед. В этом случае мы решаем вышеуказанную проблему, формируя команду типа "give 5 elements after id=7" (предоставить 5 элементов после id=7).

GrahpQL использует пагинацию на основе курсора. Настроить пагинацию с помощью библиотеки hot chocolate очень просто. Вам просто нужно добавить декоратор [UsePaging] в функции/резольверы запроса.

public class Query
{
    [UsePaging(MaxPageSize = 2)]
    public IQueryable<Customer> GetCustomers([Service] GraphQLAppDbContext graphqldbcontext)
    => graphqldbcontext.Customers.Include("Cards");
}

По умолчанию graphQL возвращает на страницу 10 элементов. Однако это можно изменить с помощью параметра MaxPagesize. Если снова посмотреть на схему graphQL после добавления атрибута UsePaging, мы увидим в ней некоторые изменения.

Чтобы получить доступ к конфигурации пагинации, в интерфейс добавляется элемент pageInfo, а к элементам сущностей мы можем обращаться через nodes (узлы).

Чтобы получить следующий элемент, нам нужно использовать индекс курсора, определенный как endcursor, и указать, какое количество элементов мы хотим получить дальше.


Материал подготовлен в преддверии старта курса "C# Developer. Professional".

Комментарии (0)