先日 ASP.NET Core で GraphQL API を実装してみた。
tnakamura.hatenablog.com
データベースから Entity Framework Core を使ってデータを取得する部分は自前で書いたわけだが、なかなか面倒だったのでなんとかしたいところ。
例えば Ruby だと graphql-ruby という gem がメジャーで、こいつは Rails をサポートしているので、
ActiveRecord と連携しやすそうだ。Entity Framework Core でも同様のものがほしい。
探せばあるもので、GraphQL.EntityFramework
というパッケージがあった。
www.nuget.org
これを使えば 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
{
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();
}
}
}
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);
}
}
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);
});
}
}
public class HelloGraphQLSchema : Schema
{
public HelloGraphQLSchema(IDependencyResolver dependencyResolver)
: base(dependencyResolver)
{
Query = dependencyResolver.Resolve<HelloGraphQLQuery>();
}
}
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;
}
}
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);
EfGraphQLConventions.RegisterConnectionTypesInContainer(services);
using (var dbContext = services.BuildServiceProvider().GetService<ApplicationDbContext>())
{
EfGraphQLConventions.RegisterInContainer(services, dbContext);
}
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)
{
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 が好みでないので、実践投入するかと聞かれたら微妙なところ。