如何使用 GraphQL .Net Core 和 Entity Framework 构建 Web API
使用 GraphQL .Net Core 和 Entity Framework 构建 Web Api
使用 GraphQL .Net Core 和 Entity Framework 构建 Web Api
在本文中我们将了解:
- 构建 Web Api,我们将学习如何使用 .Net Core CLI 搭建 Web Api,以及不同的类如何响应不同的请求
- 集成 GraphQL,我们将创建必要的辅助类来创建 Schema、解析器等。此外,我们还将学习如何使用可视化环境 GraphiQL。
- 设置实体框架 ( EF)并将类映射到表。在这里,我们将创建一个包含实体的类,该类将在数据库中创建相应的表和列。我们还将把 EF 与我们的 API 集成。
这是这篇文章的 repo:
资源
-
在 .Net Core 中构建你的第一个 GraphQL 应用
教你如何使用 GraphQL 在 .Net Core 中创建你的第一个应用。本教程将讨论查询、修改、解析器等基本主题。 -
在 .Net Core 中使用 GraphQL 构建无服务器 API
这介绍如何使用底层 GraphQL API 构建无服务器 API。 -
我基于这篇文章撰写了这篇文章,
这篇文章在很多方面都非常优秀。它确实使用了一种不同的、更冗长的方式来设置 GraphQL。至于使用哪种方法,则完全取决于你。 -
将 .Net Core 应用部署到云
这将教您如何从 VS Code 进行部署 -
免费 Azure 帐户
要部署无服务器 Azure 函数,您需要一个免费的 Azure 帐户)
构建我们的 Web Api
我们要做的第一件事是搭建一个 .Net Core 项目。我们将使用一个名为 的模板webapi
。命令如下:
dotnet new webapp -o aspnetcoreapp
这将在文件夹中创建一个 Web Api 项目aspnetcoreapp
。标志-o
指示目录的名称。因此,您可以将其替换aspnetcoreapp
为您选择的名称。
如果您之前从未在 .Net Core 中构建过 Web API,我建议您查看 中提到的 Web API 链接Resources
。不过,我还是要说一下。其核心思想是创建一个与控制器匹配的路由概念。在一个普通的 Web API 中,通常会有一个api/Products
由类处理的路由ProductsController
。在实现 GraphQL 部分时,我们会更深入地探讨这一点。
集成 GraphQL
我们将采取以下步骤来集成 GraphQL:
- 从 NuGet安装依赖项
- 使用自定义类型、查询类型和突变定义模式
- 创建响应请求的解析器函数
- 添加 Web Api 路由来响应来自 GraphiQL(我们的可视化环境)的请求
安装依赖项
首先确保我们位于项目目录中:
cd aspnetcoreapp
现在像这样安装依赖项:
dotnet add package GraphQL --version 2.4.0
dotnet add package graphiql --version 1.2.0
该包GraphQL
将为我们提供设置模式和定义解析器所需的核心库。graphiql
包是一个可视化环境,我们将使用它来展示开发人员的体验有多么出色。
设置架构
Graphql
像这样创建一个目录:
mkdir Graphql
现在创建一个文件Schema.cs
并赋予它以下内容:
using GraphQL.Types;
using GraphQL;
using Api.Database;
namespace Api.Graphql
{
public class MySchema
{
private ISchema _schema { get; set; }
public ISchema GraphQLSchema
{
get
{
return this._schema;
}
}
public MySchema()
{
this._schema = Schema.For(@"
type Book {
id: ID
name: String,
genre: String,
published: Date,
Author: Author
}
type Author {
id: ID,
name: String,
books: [Book]
}
type Mutation {
addAuthor(name: String): Author
}
type Query {
books: [Book]
author(id: ID): Author,
authors: [Author]
hello: String
}
", _ =>
{
_.Types.Include<Query>();
_.Types.Include<Mutation>();
});
}
}
}
让我们分解一下我们刚才的操作。Query
和Mutation
是保留字。Query
是我们的公共 API,我们在这里输入的任何内容都可以被查询。Mutation
也是公共 API 的一部分,但它表示我们想要更改数据。我们唯一的条目是,addAuthor
这将允许我们创建一个 Author。Author
和Book
是我们刚刚定义的自定义类型,它们具有一些合适的属性。
查询
如果你还没有读过我其他关于 GraphQL 的文章,我建议你先看看资源部分,不过这里还是简单介绍一下如何在 GraphQL 中进行查询。根据上面的模式,我们可以查询books
。它看起来可能像这样:
{
books {
name,
genre,
published
}
}
这将给出如下响应:
{
"data": {
"books" : [{
"name": "IT",
"genre": "Horror",
"published": "1994"
}]
}
}
GraphQL 的一大优点是它允许我们深入了解并请求更多数据,因此我们可以要求它在上面的查询中列出作者,如下所示:
{
books {
name,
genre,
published,
author {
name
}
}
}
并给出相应的答案:
{
"data": {
"books" : [{
"name": "IT",
"genre": "Horror",
"published": "1994",
"author": {
"name": "Stephen King"
}
}]
}
}
定义解析器
在定义解析器之前,我们需要几种类型。我们需要创建Author
和Book
。
创建类型
Author.cs
首先在目录下创建一个文件Database
。赋予它以下内容:
// Database/Author.cs
using System.Collections.Generic;
namespace Api.Database
{
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public List<Book> Books { get; set; }
}
}
现在在目录Book.cs
下创建那个Database
:
// Database/Book.cs
namespace Api.Database
{
public class Book
{
public string Id { get; set; }
public string Name { get; set; }
public bool Published { get; set; }
public string Genre { get; set; }
public int AuthorId { get; set; }
public Author Author { get; set; }
}
}
创建查询解析器
现在让我们定义相应的解析器。我们在 Schema.cs 中提到了Query
和Mutation
,但我们还没有定义这些类。让我们首先创建Query.cs
并赋予它以下内容:
// Graphql/Query.cs
using System.Collections.Generic;
using GraphQL;
using System.Linq;
using Api.Database;
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
namespace Api.Graphql
{
public class Query
{
[GraphQLMetadata("books")]
public IEnumerable<Book> GetBooks()
{
return Enumerable.Empty<Books>();
}
[GraphQLMetadata("authors")]
public IEnumerable<Author> GetAuthors()
{
return Enumerable.Empty<Authors>();
}
[GraphQLMetadata("author")]
public Author GetAuthor(int id)
{
return null;
}
[GraphQLMetadata("hello")]
public string GetHello()
{
return "World";
}
}
}
我们在上面创建了一个类,用于处理模式中的每个查询请求。我们还创建了一个方法,用于对应所有可查询的内容。装饰器GraphQLMetadata
帮助我们将模式中的内容映射到一个方法,即解析器。例如,我们可以看到它author(id: ID): Author
是如何映射到类中的以下代码的Query
:
[GraphQLMetadata("author")]
public Author GetAuthor(int id)
{
return null;
}
创建突变解析器
我们还需要定义一个解析器,即Mutation
。让我们Mutation.cs
使用以下内容创建:
using Api.Database;
using GraphQL;
namespace Api.Graphql
{
[GraphQLMetadata("Mutation")]
public class Mutation
{
[GraphQLMetadata("addAuthor")]
public Author Add(string name)
{
return null;
}
}
}
添加 GraphQL 路由
对于 GraphQL 来说,关键在于只使用一条路由/graphql
,并由前端和后端协商返回什么内容。我们将做两件事:
- 将 GraphiQL 映射到
/graphql
- 创建一个控制器来响应
/graphql
地图 GraphiQL
GraphiQL 是一个可视化环境,我们从 NuGet 安装。为了使用它,我们需要打开它Startup.cs
,并在方法中Configure()
添加以下代码:
app.UseGraphiQl("/graphql");
注意,请确保在之前添加上述行app.UseMvc();
创建 GraphQL 控制器
在目录下Controllers
创建一个文件GraphqlController.cs
。让我们逐步建立这个文件。让我们从类定义开始:
// GraphqlController.cs
using System.Threading.Tasks;
using GraphQL;
using Microsoft.AspNetCore.Mvc;
using Api.Graphql;
[Route("graphql")]
[ApiController]
public class GraphqlController: ControllerBase
{
}
通过使用装饰器,Route
我们可以将特定路由映射到类,而无需依赖默认约定。如您所见,我们为其提供了参数graphql
以确保其匹配/graphql
。我们还为该类添加了装饰器ApiController
,我们需要对所有 API 控制器执行此操作,以便它们能够响应请求。
接下来,我们需要一个方法来处理请求。关于 GraphQL 的一个好处是,所有请求都使用动词POST
,因此,我们需要像这样设置一个方法:
[HttpPost]
public async Task<ActionResult> Post([FromBody] GraphQLQuery query)
{
return null;
}
装饰器HttpPost
确保我们能够响应POST
请求。让我们仔细看看输入参数query
。它使用装饰器FromBody
从发布的 Body 中解析出值,并尝试将其转换为类型GraphQLQuery
。
我想到两个问题:它是什么?GraphQLQuery
为什么我们需要它?
GraphQLQuery
是我们需要定义的类,因此让我们通过创建来实现/Graphql/GraphQLQuery.cs
:
using Newtonsoft.Json.Linq;
namespace Api.Graphql
{
public class GraphQLQuery
{
public string OperationName { get; set; }
public string NamedQuery { get; set; }
public string Query { get; set; }
public JObject Variables { get; set; }
}
}
第二个问题:我们为什么需要它?它需要看起来像这样,因为我们要将它与我们的可视化环境 GraphiQL 集成。一旦我们开始使用 GraphiQL,我们就有理由回到这个结构,看看上面的结构是如何填充的。
让我们为控制器添加其余的实现:
// GraphqlController.cs
using System.Threading.Tasks;
using Api.Graphql;
using GraphQL;
using Microsoft.AspNetCore.Mvc;
namespace graphql_ef.Controllers
{
[Route("graphql")]
[ApiController]
public class GraphqlController: ControllerBase
{
[HttpPost]
public async Task<ActionResult> Post([FromBody] GraphQLQuery query)
{
var schema = new MySchema();
var inputs = query.Variables.ToInputs();
var result = await new DocumentExecuter().ExecuteAsync(_ =>
{
_.Schema = schema.GraphQLSchema;
_.Query = query.Query;
_.OperationName = query.OperationName;
_.Inputs = inputs;
});
if (result.Errors?.Count > 0)
{
return BadRequest();
}
return Ok(result);
}
}
}
上面我们从输入中读取我们需要的内容query
并将其传递给DocumentExecuter
最终得到结果。
现在我们已经准备好一切,可以开始尝试我们的 Api 了Debug/Start Debugging
。接下来,你应该会看到以下内容:
这里我们创建了三个查询AllBooks
,Author
和AllBooksWithAuthor
。
我们可以轻松运行其中一个查询,点击Play
按钮,这使我们能够选择一个特定的查询:
运行查询我们得到以下结果:
不过我们并不感到惊讶,因为我们只是把答案去掉了,返回了一个空数组。在修复这个问题并连接数据库之前,我们先来谈谈我们的 GraphiQL 环境。
我没有提到的一件很棒的事情是,我们在编写查询或变异时具有自动完成支持,因此我们可以在输入可用的资源和列时轻松获取信息:
显然,我们可以编写多个查询,然后选择所需的查询。此外,我们可以查看右侧窗格,查看我们的模式定义是否可浏览:
单击Docs
链接将显示从顶层开始的所有类型:
然后,我们可以根据需要进行深入研究,看看我们可以查询什么、我们有哪些自定义类型等等:
使用实体框架添加数据库
现在一切已正常运行,让我们定义数据库并用数据库调用替换解析器方法中的存根答案。
为了实现这一切,我们将采取以下措施:
- 在代码中定义一个数据库,我们通过创建一个继承自的类来实现
DbContext
,并确保它具有以下类型的字段DbSet
- 定义我们想要在数据库中使用的模型,实际上我们在创建
Author
和时已经完成了这一步Book
。 - 设置并配置数据库类型,我们将使用内存数据库类型,但稍后我们绝对可以将其更改为 Sql Server 或 MySql 或我们需要的任何数据库类型
- 为数据库提供种子,为了这个例子,我们需要一些初始数据,以便在查询时得到一些返回
- 将解析器方法中的存根代码替换为对数据库的实际调用
在代码中定义数据库
我们使用一种称为“代码优先”的方法。这意味着我们创建一个带有字段的类,这些字段将成为数据库中的表。让我们创建一个文件StoreContext.cs
并为其添加以下内容:
using Microsoft.EntityFrameworkCore;
namespace Api.Database
{
public class StoreContext : DbContext
{
public StoreContext(){}
public StoreContext(DbContextOptions<StoreContext> options)
: base(options)
{ }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseInMemoryDatabase("BooksDb");
}
public DbSet<Book> Books { get; set; }
public DbSet<Author> Authors { get; set; }
}
}
这两个字段Books
的Authors
类型分别为DbSet<Book>
和DbSet<Author>
,它们将成为我们数据库中的表。方法OnConfiguring()
是我们设置数据库的地方BooksDb
,并且我们还指定要使用方法 将其存储在内存数据库中UseInMemoryDatabase()
。如果我们希望它持久化到实际的数据库中,我们可以将其更改为其他类型。
种子数据库
现在这不是我们必须要做的步骤,但在开始查询时有一些数据总是好的。为此,我们将打开Program.cs
并向方法中添加以下内容Main()
:
using(var db = new StoreContext())
{
var authorDbEntry = db.Authors.Add(
new Author
{
Name = "Stephen King",
}
);
db.SaveChanges();
db.Books.AddRange(
new Book
{
Name = "IT",
Published = true,
AuthorId = authorDbEntry.Entity.Id,
Genre = "Mystery"
},
new Book
{
Name = "The Langoleers",
Published = true,
AuthorId = authorDbEntry.Entity.Id,
Genre = "Mystery"
}
);
db.SaveChanges();
}
以上将创建一位作者和两本书。
替换存根代码
现在到了有趣的部分。我们将用对数据库和实体框架的实际调用替换我们的存根代码。
我们需要更改两个文件,Query.cs
和Mutation.cs
。让我们从开始吧Query.cs
。
查询.cs
打开Query.cs
Graphql 目录下的文件并将其内容替换为以下内容:
// Graphql/Query.cs
using System.Collections.Generic;
using GraphQL;
using System.Linq;
using Api.Database;
using Microsoft.EntityFrameworkCore;
namespace Api.Graphql
{
public class Query
{
[GraphQLMetadata("books")]
public IEnumerable<Book> GetBooks()
{
using(var db = new StoreContext())
{
return db.Books
.Include(b => b.Author)
.ToList();
}
}
[GraphQLMetadata("authors")]
public IEnumerable<Author> GetAuthors()
{
using (var db = new StoreContext())
{
return db.Authors
.Include(a => a.Books)
.ToList();
}
}
[GraphQLMetadata("author")]
public Author GetAuthor(int id)
{
using (var db = new StoreContext())
{
return db.Authors
.Include(a => a.Books)
.SingleOrDefault(a => a.Id == id);
}
}
[GraphQLMetadata("hello")]
public string GetHello()
{
return "World";
}
}
}
上面我们用对数据库的调用替换了所有存根代码。让我们来看看相关的方法:
获取图书
[GraphQLMetadata("books")]
public IEnumerable<Book> GetBooks()
{
using(var db = new StoreContext())
{
return db.Books
.Include(b => b.Author)
.ToList();
}
}
上面我们从数据库中选取了所有图书,并确保包含该Author
属性。这样我们就可以支持如下查询:
{
books {
name,
author {
name
}
}
}
获取作者
[GraphQLMetadata("authors")]
public IEnumerable<Author> GetAuthors()
{
using (var db = new StoreContext())
{
return db.Authors
.Include(a => a.Books)
.ToList();
}
}
这里我们从数据库中选取了所有作者,并包含了该作者撰写的所有书籍。这样我们就可以支持如下查询:
{
authors {
name,
books {
name,
published
}
}
}
突变.cs
打开Mutation.cs
并将其存根代码替换为:
using Api.Database;
using GraphQL;
namespace Api.Graphql
{
[GraphQLMetadata("Mutation")]
public class Mutation
{
[GraphQLMetadata("addAuthor")]
public Author Add(string name)
{
using(var db = new StoreContext())
{
var author = new Author(){ Name = name };
db.Authors.Add(author);
db.SaveChanges();
return author;
}
}
}
}
addAuthor(name: String): Author
正如您上面看到的,我们通过创建新作者、将其保存到数据库然后返回实体来支持变异。
在 GraphiQL 中测试
最后一件事就是在可视化界面上测试一下。点击一下Debug/Start Debugging
,看看会发生什么:
看起来我们的书籍列表查询工作正常,它为我们提供了数据库中的标题。
接下来,我们尝试进行变异:
从上面的结果来看,一切似乎进展顺利,太棒了!:)
概括
这是一篇相当雄心勃勃的文章。我们成功地在 Web API 中创建了一个 GraphQL API。我们还成功地引入了一个使用 Entity Framework 访问的数据库。
最大的收获不仅在于设置简单,还在于 GraphiQL 出色的可视化环境。它不仅帮助我们实现了自动补全,还记录了我们的架构,帮助我们验证查询等等。
尽管这篇文章有点长,但希望您觉得它有用。
最后,我想说的是,webapi
项目类型自带了依赖注入,但我没能用上。主要原因是,这种设置 GraphQL 的方式意味着我们无法控制实例化Query
和Mutation
。你可以在参考资料部分看到,我引用了一篇文章,它实现了我们今天在这里做的事情,并成功地使用了依赖注入 (DI)。然而,你必须以一种截然不同的方式设置 GraphQL 模式,在我看来,这种方式要冗长得多。