GraphQL でコレクションデータの取得やページネーションを実現する場合、 Relay 由来の Connection や Edge といったインタフェースを定義するのが定石になっているみたい。
GraphQL.NET はそれらをサポートしていて、ConnectionType や EdgeType といった型を提供してくれていたので、試しにやってみた。
using System.Linq; using GraphQL; using GraphQL.Server; using GraphQL.Server.Ui.Playground; using GraphQL.Types; using GraphQL.Types.Relay.DataObjects; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace ConnectionSample { public class Player { public int Id { get; set; } public string Name { get; set; } public int Number { get; set; } } public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Player> Players => Set<Player>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Player>(e => { e.Property(x => x.Id).IsRequired(); e.Property(x => x.Name).IsRequired(); e.Property(x => x.Number).IsRequired(); e.HasKey(x => x.Id); }); } // テスト用のデータを登録する public void EnsureSeedData() { if (!Players.Any()) { foreach (var x in Enumerable.Range(1, 200)) { Players.Add(new Player { Name = $"プレーヤー{x}", Number = x, }); } SaveChanges(); } } } public class PlayerType : ObjectGraphType<Player> { public PlayerType() : base() { Field(x => x.Id); Field(x => x.Name); Field(x => x.Number); } } public class SampleQuery : ObjectGraphType { public SampleQuery() : base() { Connection<PlayerType>() .Name("players") .PageSize(50) .Resolve(context => { var dbContext = (ApplicationDbContext)context.UserContext; var query = dbContext.Players.AsQueryable(); if (!string.IsNullOrEmpty(context.After) && int.TryParse(context.After, out var lastPlayerId)) { query = query.Where(x => x.Id > lastPlayerId); } if (context.First != null) { query = query.Take(context.First.Value); } else { query = query.Take(context.PageSize.Value); } var players = query.ToList(); return new Connection<Player> { PageInfo = new PageInfo { StartCursor = players.FirstOrDefault()?.Id.ToString(), EndCursor = players.LastOrDefault()?.Id.ToString(), }, Edges = players.Select(x => new Edge<Player> { Cursor = x.Id.ToString(), Node = x, }).ToList(), }; }); } } public class SampleSchema : Schema { public SampleSchema(IDependencyResolver dependencyResolver) : base(dependencyResolver) { Query = dependencyResolver.Resolve<SampleQuery>(); } } public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); // GraphQL Server 用 services.AddGraphQL() .AddUserContextBuilder(httpContext => { return httpContext.RequestServices .GetService<ApplicationDbContext>(); }) .AddRelayGraphTypes(); // ConnectionType<T>, EdgeType<T>, PageInfoType 等を登録 // GraphQL 用 services.AddSingleton<PlayerType>(); services.AddSingleton<SampleQuery>(); services.AddSingleton<ISchema, SampleSchema>(); services.AddSingleton<IDocumentExecuter, DocumentExecuter>(); services.AddSingleton<IDependencyResolver>(x => new FuncDependencyResolver(x.GetService)); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext dbContext) { // GraphQL Server を使う app.UseGraphQL<ISchema>("/graphql"); // GraphQL Playground を使う app.UseGraphQLPlayground(new GraphQLPlaygroundOptions { Path = "/ui/playground", }); // テスト用データベースが無ければ作る dbContext.Database.EnsureCreated(); dbContext.EnsureSeedData(); } } public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); } }
GraphQL API を起動して、Web ブラウザから Playground にアクセスし、コレクションデータを取得するクエリを実行してみたのがこちら。
ちゃんと動いた。次のページのデータを取得する場合は、endCursor の値を引数 after に指定したクエリを実行すればいい。
GraphQL.EntityFramework を使ったときは、リゾルバで IQueryable<T>
を返してやれば良きに計らってくれたのに対し、GraphQL.NET だけだと自前で書かないといけないのは手間だな。