先日 ASP.NET Core で GraphQL API を実装してみた。
データベースから Entity Framework Core を使ってデータを取得する部分は自前で書いたわけだが、なかなか面倒だったのでなんとかしたいところ。
例えば Ruby だと graphql-ruby という gem がメジャーで、こいつは Rails をサポートしているので、 ActiveRecord と連携しやすそうだ。Entity Framework Core でも同様のものがほしい。
探せばあるもので、GraphQL.EntityFramework
というパッケージがあった。
これを使えば ASP.NET Core + Entity Framework Core で GraphQL を実装するのが少し楽になりそう。先日のサンプルを書き変えてみた。
using GraphiQl; using GraphQL; using GraphQL.EntityFramework; using GraphQL.Types; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; namespace HelloGraphQL { //============================================ // 1. Entity Framework Core で使うクラスを定義 //============================================ public class Team { public int Id { get; set; } public string Name { get; set; } public IList<Player> Players { get; set; } } public class Player { public int Id { get; set; } public string Name { get; set; } public string Position { get; set; } public int Number { get; set; } public int TeamId { get; set; } public Team Team { get; set; } } public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Team> Teams => Set<Team>(); public DbSet<Player> Players => Set<Player>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Team>(e => { e.Property(x => x.Id).IsRequired(); e.Property(x => x.Name).IsRequired(); e.HasKey(x => x.Id); e.HasMany(x => x.Players); }); modelBuilder.Entity<Player>(e => { e.Property(x => x.Id).IsRequired(); e.Property(x => x.Name).IsRequired(); e.Property(x => x.Number).IsRequired(); e.Property(x => x.Position).IsRequired(); e.Property(x => x.TeamId).IsRequired(); e.HasKey(x => x.Id); e.HasOne(x => x.Team); }); } // テスト用のデータを登録する public void EnsureSeedData() { if (!Teams.Any()) { Teams.Add(new Team { Name = "バルセロナ", Players = new List<Player> { new Player { Name = "メッシ", Number = 10, Position = "FW", }, new Player { Name = "スアレス", Number = 9, Position = "FW", }, }, }); Teams.Add(new Team { Name = "レアルマドリード", Players = new List<Player> { new Player { Name = "ベイル", Number = 11, Position = "FW", }, new Player { Name = "モドリッチ", Number = 10, Position = "MF", }, }, }); SaveChanges(); } } } //============================== // 2. GraphQL で使うクラスを定義 //============================== // Player に対応する GraphQL の型 public class PlayerType : EfObjectGraphType<Player> { public PlayerType(IEfGraphQLService graphQLService) : base(graphQLService) { Field(x => x.Id); Field(x => x.Name); Field(x => x.Position); Field(x => x.Number); Field(x => x.TeamId); } } // Team に対応する GraphQL の型 public class TeamType : EfObjectGraphType<Team> { public TeamType(IEfGraphQLService graphQLService) : base(graphQLService) { Field(x => x.Id); Field(x => x.Name); Field<ListGraphType<PlayerType>>( name: "players", resolve: context => context.Source.Players); } } // クエリを定義 public class HelloGraphQLQuery : EfObjectGraphType { public HelloGraphQLQuery(IEfGraphQLService graphQLService) : base(graphQLService) { AddQueryConnectionField<TeamType, Team>( name: "teams", resolve: context => { var dbContext = (ApplicationDbContext)context.UserContext; return dbContext.Teams.Include(t => t.Players); }); } } // GraphQL のスキーマを定義 public class HelloGraphQLSchema : Schema { public HelloGraphQLSchema(IDependencyResolver dependencyResolver) : base(dependencyResolver) { // 今回はクエリだけ Query = dependencyResolver.Resolve<HelloGraphQLQuery>(); } } //==================================================== // 3. ASP.NET Core MVC で GraphQL エンドポイントを実装 //==================================================== // クライアントから送信されてきた GraphQL をバインドするモデル public class GraphQLModel { public string OperationName { get; set; } public string Query { get; set; } public JObject Variables { get; set; } } [Route("graphql")] [ApiController] public class GraphQLController : ControllerBase { readonly IDocumentExecuter documentExecuter; readonly ISchema schema; readonly ApplicationDbContext dbContext; public GraphQLController( IDocumentExecuter documentExecuter, ISchema schema, ApplicationDbContext dbContext) { this.documentExecuter = documentExecuter; this.schema = schema; this.dbContext = dbContext; } [HttpPost] public async Task<ExecutionResult> Post([FromBody] GraphQLModel model) { var result = await documentExecuter.ExecuteAsync(new ExecutionOptions { Schema = schema, OperationName = model.OperationName, Query = model.Query, Inputs = model.Variables?.ToInputs(), UserContext = dbContext, }); if (0 < result.Errors?.Count) { Response.StatusCode = (int)HttpStatusCode.BadRequest; } return result; } } //============================================================== // 4. いつものように ASP.NET Core 用の Startup と Program を作成 //============================================================== 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"))); services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); //-------------------- // ここから GraphQL 用 //-------------------- EfGraphQLConventions.RegisterConnectionTypesInContainer(services); using (var dbContext = services.BuildServiceProvider().GetService<ApplicationDbContext>()) { EfGraphQLConventions.RegisterInContainer(services, dbContext); } // GraphQL 用のクラスを DI コンテナに登録しておく必要がある。 services.AddSingleton<HelloGraphQLQuery>(); services.AddSingleton<TeamType>(); services.AddSingleton<PlayerType>(); services.AddSingleton<IDocumentExecuter, DocumentExecuter>(); services.AddSingleton<ISchema, HelloGraphQLSchema>(); services.AddSingleton<IDependencyResolver>( provider => new FuncDependencyResolver(provider.GetService)); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext context) { // GraphiQL をホストして手軽に GraphQL を試せるようにする app.UseGraphiQl("/graphiql", "/graphql"); app.UseMvc(); // テスト用データベースが無ければ作る context.Database.EnsureDeleted(); context.Database.EnsureCreated(); context.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>(); } }
デバッグ実行し Web ブラウザで /graqhiql
にアクセス。
全チーム取得するクエリを実行してみると、無事データベースからデータを引っ張ってこれた。
GraphQL.EntityFramework がフィルタやソートのためのパラメータを自動で生成してくれるから、自分で実装する手間が省けるのは助かる。ただ、それよりも追加で覚えることが多いのと、API が好みでないので、実践投入するかと聞かれたら微妙なところ。