如何使用 GraphQL .Net Core 和 Entity Framework 构建 Web API 使用 GraphQL .Net Core 和 Entity Framework 构建 Web API

2025-05-25

如何使用 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:

https://github.com/softchris/graphql-ef-demo

 资源

 构建我们的 Web Api

我们要做的第一件事是搭建一个 .Net Core 项目。我们将使用一个名为 的模板webapi。命令如下:



dotnet new webapp -o aspnetcoreapp


Enter fullscreen mode Exit fullscreen mode

这将在文件夹中创建一个 Web Api 项目aspnetcoreapp。标志-o指示目录的名称。因此,您可以将其替换aspnetcoreapp为您选择的名称。

如果您之前从未在 .Net Core 中构建过 Web API,我建议您查看 中提到的 Web API 链接Resources。不过,我还是要说一下。其核心思想是创建一个与控制器匹配的路由概念。在一个普通的 Web API 中,通常会有一个api/Products由类处理的路由ProductsController。在实现 GraphQL 部分时,我们会更深入地探讨这一点。

 集成 GraphQL

我们将采取以下步骤来集成 GraphQL:

  1. 从 NuGet安装依赖项
  2. 使用自定义类型、查询类型和突变定义模式
  3. 创建响应请求的解析器​​函数
  4. 添加 Web Api 路由来响应来自 GraphiQL(我们的可视化环境)的请求

安装依赖项

首先确保我们位于项目目录中:



cd aspnetcoreapp


Enter fullscreen mode Exit fullscreen mode

现在像这样安装依赖项:



dotnet add package GraphQL --version 2.4.0
dotnet add package graphiql --version 1.2.0


Enter fullscreen mode Exit fullscreen mode

该包GraphQL将为我们提供设置模式和定义解析器所需的核心库。graphiql包是一个可视化环境,我们将使用它来展示开发人员的体验有多么出色。

 设置架构

Graphql像这样创建一个目录:



mkdir Graphql


Enter fullscreen mode Exit fullscreen mode

现在创建一个文件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>();
      });
    }

  }
}


Enter fullscreen mode Exit fullscreen mode

让我们分解一下我们刚才的操作。QueryMutation是保留字。Query是我们的公共 API,我们在这里输入的任何内容都可以被查询。Mutation也是公共 API 的一部分,但它表示我们想要更改数据。我们唯一的条目是,addAuthor这将允许我们创建一个 Author。AuthorBook是我们刚刚定义的自定义类型,它们具有一些合适的属性。

查询

如果你还没有读过我其他关于 GraphQL 的文章,我建议你先看看资源部分,不过这里还是简单介绍一下如何在 GraphQL 中进行查询。根据上面的模式,我们可以查询books。它看起来可能像这样:



{ 
  books { 
    name,
    genre,
    published
  }
}


Enter fullscreen mode Exit fullscreen mode

这将给出如下响应:



{
  "data": {
    "books" : [{
      "name": "IT",
      "genre": "Horror",
      "published": "1994"
    }]
  } 
}


Enter fullscreen mode Exit fullscreen mode

GraphQL 的一大优点是它允许我们深入了解并请求更多数据,因此我们可以要求它在上面的查询中列出作者,如下所示:



{
  books {
    name, 
    genre,
    published,
    author {
      name
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

并给出相应的答案:



{
  "data": {
    "books" : [{
      "name": "IT",
      "genre": "Horror",
      "published": "1994",
      "author": {
        "name": "Stephen King"
      }
    }]
  } 
}


Enter fullscreen mode Exit fullscreen mode

 定义解析器

在定义解析器之前,我们需要几种类型。我们需要创建AuthorBook

创建类型

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; }
  }
}


Enter fullscreen mode Exit fullscreen mode

现在在目录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; }
  }
}


Enter fullscreen mode Exit fullscreen mode

创建查询解析器

现在让我们定义相应的解析器。我们在 Schema.cs 中提到了QueryMutation,但我们还没有定义这些类。让我们首先创建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";
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

我们在上面创建了一个类,用于处理模式中的每个查询请求。我们还创建了一个方法,用于对应所有可查询的内容。装饰器GraphQLMetadata帮助我们将模式中的内容映射到一个方法,即解析器。例如,我们可以看到它author(id: ID): Author是如何映射到类中的以下代码的Query



[GraphQLMetadata("author")]
public Author GetAuthor(int id)
{
  return null;
}


Enter fullscreen mode Exit fullscreen mode

创建突变解析器

我们还需要定义一个解析器,即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;
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

添加 GraphQL 路由

对于 GraphQL 来说,关键在于只使用一条路由/graphql,并由前端和后端协商返回什么内容。我们将做两件事:

  1. 将 GraphiQL 映射到/graphql
  2. 创建一个控制器来响应/graphql

地图 GraphiQL

GraphiQL 是一个可视化环境,我们从 NuGet 安装。为了使用它,我们需要打开它Startup.cs,并在方法中Configure()添加以下代码:



app.UseGraphiQl("/graphql");


Enter fullscreen mode Exit fullscreen mode

注意,请确保在之前添加上述行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 
{
}


Enter fullscreen mode Exit fullscreen mode

通过使用装饰器,Route我们可以将特定路由映射到类,而无需依赖默认约定。如您所见,我们为其提供了参数graphql以确保其匹配/graphql。我们还为该类添加了装饰器ApiController,我们需要对所有 API 控制器执行此操作,以便它们能够响应请求。

接下来,我们需要一个方法来处理请求。关于 GraphQL 的一个好处是,所有请求都使用动词POST,因此,我们需要像这样设置一个方法:



[HttpPost]
public async Task<ActionResult> Post([FromBody] GraphQLQuery query) 
{
  return null;
}



Enter fullscreen mode Exit fullscreen mode

装饰器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; }
  }
}


Enter fullscreen mode Exit fullscreen mode

第二个问题:我们为什么需要它?它需要看起来像这样,因为我们要将它与我们的可视化环境 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);
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

上面我们从输入中读取我们需要的内容query并将其传递给DocumentExecuter最终得到结果。

现在我们已经准备好一切,可以开始尝试我们的 Api 了Debug/Start Debugging。接下来,你应该会看到以下内容:

这里我们创建了三个查询AllBooksAuthorAllBooksWithAuthor

我们可以轻松运行其中一个查询,点击Play按钮,这使我们能够选择一个特定的查询:

运行查询我们得到以下结果:

不过我们并不感到惊讶,因为我们只是把答案去掉了,返回了一个空数组。在修复这个问题并连接数据库之前,我们先来谈谈我们的 GraphiQL 环境。

我没有提到的一件很棒的事情是,我们在编写查询或变异时具有自动完成支持,因此我们可以在输入可用的资源和列时轻松获取信息:

显然,我们可以编写多个查询,然后选择所需的查询。此外,我们可以查看右侧窗格,查看我们的模式定义是否可浏览:

单击Docs链接将显示从顶层开始的所有类型:

然后,我们可以根据需要进行深入研究,看看我们可以查询什么、我们有哪些自定义类型等等:

 使用实体框架添加数据库

现在一切已正常运行,让我们定义数据库并用数据库调用替换解析器方法中的存根答案。

为了实现这一切,我们将采取以下措施:

  1. 在代码中定义一个数据库,我们通过创建一个继承自的类来实现DbContext,并确保它具有以下类型的字段DbSet
  2. 定义我们想要在数据库中使用的模型,实际上我们在创建Author和时已经完成了这一步Book
  3. 设置并配置数据库类型,我们将使用内存数据库类型,但稍后我们绝对可以将其更改为 Sql Server 或 MySql 或我们需要的任何数据库类型
  4. 为数据库提供种子,为了这个例子,我们需要一些初始数据,以便在查询时得到一些返回
  5. 将解析器方法中的存根代码替换为对数据库的实际调用

 在代码中定义数据库

我们使用一种称为“代码优先”的方法。这意味着我们创建一个带有字段的类,这些字段将成为数据库中的表。让我们创建一个文件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; }
  }
}


Enter fullscreen mode Exit fullscreen mode

这两个字段BooksAuthors类型分别为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();
}


Enter fullscreen mode Exit fullscreen mode

以上将创建一位作者和两本书。

替换存根代码

现在到了有趣的部分。我们将用对数据库和实体框架的实际调用替换我们的存根代码。

我们需要更改两个文件,Query.csMutation.cs。让我们从开始吧Query.cs

查询.cs

打开Query.csGraphql 目录下的文件并将其内容替换为以下内容:



// 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";
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

上面我们用对数据库的调用替换了所有存根代码。让我们来看看相关的方法:

获取图书



[GraphQLMetadata("books")]
public IEnumerable<Book> GetBooks()
{
  using(var db = new StoreContext())
  {
    return db.Books
    .Include(b => b.Author)
    .ToList();
  }
}


Enter fullscreen mode Exit fullscreen mode

上面我们从数据库中选取了所有图书,并确保包含该Author属性。这样我们就可以支持如下查询:



{
  books {
    name,
    author {
      name
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

获取作者



[GraphQLMetadata("authors")]
public IEnumerable<Author> GetAuthors() 
{
  using (var db = new StoreContext())
  {
    return db.Authors
    .Include(a => a.Books)
    .ToList();
  }
}


Enter fullscreen mode Exit fullscreen mode

这里我们从数据库中选取了所有作者,并包含了该作者撰写的所有书籍。这样我们就可以支持如下查询:



{
  authors {
    name,
    books {
      name,
      published
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

突变.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;
      }
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

addAuthor(name: String): Author正如您上面看到的,我们通过创建新作者、将其保存到数据库然后返回实体来支持变异。

在 GraphiQL 中测试

最后一件事就是在可视化界面上测试一下。点击一下Debug/Start Debugging,看看会发生什么:

看起来我们的书籍列表查询工作正常,它为我们提供了数据库中的标题。

接下来,我们尝试进行变异:

从上面的结果来看,一切似乎进展顺利,太棒了!:)

概括

这是一篇相当雄心勃勃的文章。我们成功地在 Web API 中创建了一个 GraphQL API。我们还成功地引入了一个使用 Entity Framework 访问的数据库。

最大的收获不仅在于设置简单,还在于 GraphiQL 出色的可视化环境。它不仅帮助我们实现了自动补全,还记录了我们的架构,帮助我们验证查询等等。

尽管这篇文章有点长,但希望您觉得它有用。

最后,我想说的是,webapi项目类型自带了依赖注入,但我没能用上。主要原因是,这种设置 GraphQL 的方式意味着我们无法控制实例化QueryMutation。你可以在参考资料部分看到,我引用了一篇文章,它实现了我们今天在这里做的事情,并成功地使用了依赖注入 (DI)。然而,你必须以一种截然不同的方式设置 GraphQL 模式,在我看来,这种方式要冗长得多。

文章来源:https://dev.to/dotnet/how-you-can-build-a-web-api-using-graphql-net-core-and-entity-framework-1ago
PREV
为什么要使用 Blazor 构建单页应用程序
NEXT
对于回归的 .NET 开发人员来说,C# 的新功能非常棒