尝试在Azure API Management的GraphQL API的解析器中使用Azure SQL数据源

首先

API管理是一个可以轻松管理API的服务,不仅可以创建REST API,还可以创建GraphQL的API。
在API管理中,我们已经能够将现有的REST API用作数据源来创建GraphQL的API,但现在我们也可以使用Cosmos DB和Azure SQL数据库作为数据源(*预览版阶段)。
通过使用这些数据源,我们可以直接将数据库作为API公开,而无需另外准备后端。
在本文中,我想使用Azure SQL数据库的数据源,在API管理中创建GraphQL的API。(标题像“鸟儿在天空中飞翔”一样奇怪了。)

前提

    • API Management のリソースが作成済みであること。

この記事では API Management のリソース作成手順については説明しません。
価格レベルは何でも良いと思いますが、執筆時は従量課金レベルで検証しています。

Azure SQL Database のリソースが作成済みであること

この記事では Azure SQL Database のリソース作成手順については説明しません。

[2023/08/20 时间点]
文件中虽然写着不支持按量计费级别,但看起来现在是可用的。
(最开始我在微软的问答中询问时被告知说按量计费无法使用,所以一直搁置了。但最近他们通知我说现在可以使用了。)

准备之前

我們基本上會按照文件的指示繼續進行。

 

启用 管理的 API ID

这次要使用托管身份标识连接到SQL数据库,因此我们需要启用托管身份标识。在Azure门户中查看API管理资源,并通过托管身份标识将状态更改为启用。

image.png

激活 Azure AD 对 SQL 数据库的访问

为了使用托管标识连接,需要在Azure AD中启用对SQL Database的访问。请打开SQL Server资源,然后选择Azure Active Directory > 管理员设置,在其中选择您自己的帐户并点击保存。

我认为Azure Active Directory 这个词将会在将来改为Microsoft Entra ID。

image.png

分配给SQL数据库的角色

在 API 管理的 SQL 数据库上进行角色设置。
通过 Azure 门户的查询编辑器或任何客户端工具连接到数据库,并执行以下查询。

DECLARE @identityName nvarchar(60) = '<API Managementのリソース名>';

EXECUTE(N'
CREATE USER ['+ @identityName +'] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [' + @identityName + '];
ALTER ROLE db_datawriter ADD MEMBER [' + @identityName + '];
ALTER ROLE db_ddladmin ADD MEMBER [' + @identityName + '];
');

image.png

创建表格

建立一个合适的表格,并插入适当的数据。

创建 API。

创建Schema文件

由于创建 GraphQL API 时需要选择 Schema 文件,因此需要事先创建。这次我们创建了如下所示的文件。

type Query {
  getUser(id: ID!): User!
  getUsers: UserList!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(input: UpdateUserInput!): User!
  deleteUser(id: ID!): User!
}

type User {
  id: ID!
  name: String!
  age: Int!
}

type UserList {
  items: [User!]!
}

input UpdateUserInput {
  id: ID!
  name: String!
  age: Int!
}

input CreateUserInput {
  id: ID!
  name: String!
  age: Int!
}

添加GraphQL API

image.png
image.png

创建解析器

image.png
image.png

设定政策

连接设置(通用)

为了使用托管的标识(Managed Identity)进行连接,需要在连接字符串的属性中指定 use-managed-identity=”true”。

<sql-data-source>
    <!-- 接続設定 -->
	<connection-info>
		<connection-string use-managed-identity="true">
            Server=tcp:<サーバー名>.database.windows.net,1433;Initial Catalog=<データベース名>;
        </connection-string>
	</connection-info>
    <!-- 省略 -->
</sql-data-source>

获取单个结果

如果要获取单个结果,可以采用以下策略:
(获取用户信息)

<!-- getUser -->
<sql-data-source>
	<!-- 接続設定は省略 -->
	<request single-result="true">
		<sql-statement>
            SELECT
                u.[id],
                u.[name],
                u.[age]
            FROM
                [dbo].[users] u
            WHERE
                u.[id] = @id
        </sql-statement>
		<parameters>
			<parameter name="@id">@(context.GraphQL.Arguments["id"])</parameter>
		</parameters>
	</request>
</sql-data-source>

为了明确结果集只有一条记录,请在request的属性中指定single-result=”true”。
您可以在sql-statement中使用在parameters中指定的参数。
参数可以指定为策略表达式。
context.GraphQL.Arguments包含在请求时指定的参数。
(顺便说一下,context.GraphQL.Arguments的类型是用于操作JSON对象的JToken类)

 

获取多行

如果要获取多行结果,则应遵循以下策略。(获取用户)

<!-- getUsers -->
<sql-data-source>
	<!-- 接続設定は省略 -->
	<request>
		<sql-statement>
            SELECT
                u.[id],
                u.[name],
                u.[age]
            FROM
                [dbo].[users] u
        </sql-statement>
	</request>
</sql-data-source>

如果结果集有多行,request属性中的single-result=”true”是不需要的。

更新系 – One possible paraphrase:

升级体系

如果是更新场景的话,将采用以下策略。(createUser)

<!-- createUser -->
<sql-data-source>
	<!-- 接続設定は省略 -->
	<request single-result="true">
		<sql-statement>
            INSERT INTO [dbo].[users] (
                [id],
                [name],
                [age]
            )
            OUTPUT inserted.*
            VALUES (
                @id,
                @name,
                @age
            )
        </sql-statement>
		<parameters>
			<parameter name="@id">@(context.GraphQL.Arguments["input"]["id"])</parameter>
			<parameter name="@name">@(context.GraphQL.Arguments["input"]["name"])</parameter>
			<parameter name="@age">@(context.GraphQL.Arguments["input"]["age"])</parameter>
		</parameters>
	</request>
</sql-data-source>

更新系操作的实质就是改变 SQL,但为了返回与模式定义相符合的插入记录,我们使用 OUTPUT inserted.* 来获取。
另外,我们还将 single-result=”true” 进行了设置。
如果没有使用 OUTPUT …的默认情况下,将返回插入的记录数量。

其他更新政策
表示:
updateUser:以用户的id为条件,更新用户表中的name和age字段,并返回更新后的内容。
deleteUser:以用户的id为条件,删除用户表中的数据,并返回删除的内容。

API测试 (API de

您可以选择API管理的API,并通过Test选项卡执行API测试。
根据Schema定义,在左侧会显示一个列表,并且自动组装查询条件通过勾选复选框。
点击Send或Trace将发送请求。
Trace会输出跟踪信息,对于调试非常有用。

image.png

给你额外的东西

自定义响应

您可以使用set-body来自定义响应的格式。

<!-- getUsers -->
<sql-data-source>
	<!-- 接続設定は省略 -->
	<request>
		<sql-statement>
            SELECT
                u.[id],
                u.[name],
                u.[age]
            FROM
                [dbo].[users] u
        </sql-statement>
	</request>
+   <response>
+       <set-body template="liquid">
+           [
+               {% JSONArrayFor user in body.items %}
+               {
+                   "id": "{{ user.id }}",
+                   "name": "{{ user.name }}",
+                   "age": {{ user.age }}
+               }
+               {% endJSONArrayFor %}
+           ]
+       </set-body>
+   </response>
</sql-data-source>

最初的格式如下

{
  "items": [
    {
      "id": "user001",
      "name": "Name001",
      "age": 10,
    },
    ...
  ]
}

我們對其進行了自定義設置。

[
  {
    "id": "user001",
    "name": "Name001",
    "age": 10,
  },
  ...
]

在 set-body 中,您可以使用 Liquid 模板引擎的语法进行编写。
看起来 Liquid 也在 Power Pages 中被采用了。

 

指定可选参数

使用政策表达

我认为你可能想要定义可选参数,并且只在被指定时才包含在条件中。
在这种情况下,你需要动态地构建 SQL,并且可以通过使用策略表达式来实现。

<!-- getUsers -->
<sql-data-source>
	<!-- 接続設定は省略 -->
   <request single-result="true">
+       <sql-statement>
+       @{
+           var sql = new StringBuilder(@"
+               SELECT
+                   u.[id],
+                   u.[name],
+                   u.[age]
+               FROM
+                   users u
+           ");
+
+           var conditions = new List<string>();
+           if (context.GraphQL.Arguments["name"].Value<string>() != null)
+           {
+               conditions.Add("u.[name] LIKE '%' + @name + '%'");
+           }
+           if (context.GraphQL.Arguments["age"].Value<int?>() != null)
+           {
+               conditions.Add("u.[age] >= @age");
+           }
+
+           if (conditions.Count > 0)
+           {
+               sql.Append($" WHERE {string.Join(" AND ", conditions)}");
+           }
+
+           return sql.ToString();
+       }
+       </sql-statement>
+       <parameters>
+           <parameter name="@name">@(context.GraphQL.Arguments["name"].Value<string>() ?? "empty")</parameter>
+           <parameter name="@age">@(context.GraphQL.Arguments["age"].Value<int?>() ?? 0)</parameter>
+       </parameters>
  </request>
</sql-data-source>

当你用 @{} 括起来,你可以像普通的 C# 代码一样以多行形式进行编写。
返回的字符串将成为 SQL 查询。

如果将参数写成下面这样,不指定的情况下会被怒斥为“必需的!”

<parameter name="@name">@(context.GraphQL.Arguments["name"])</parameter>
<parameter name="@age">@(context.GraphQL.Arguments["age"])</parameter>

如果未指定,将设置一个虚拟值。
虽然可能有其他方法,但我不清楚。
如果您知道更简洁的方法,请务必告诉我。

<parameter name="@name">@(context.GraphQL.Arguments["name"].Value<string>() ?? "empty")</parameter>
<parameter name="@age">@(context.GraphQL.Arguments["age"].Value<int?>() ?? 0)</parameter>

使用液体模板

有一个可以动态构建 SQL 的方法是使用在 set-body 中使用 Liquid 模板的方法。

<!-- getUsers -->
<sql-data-source>
	<!-- 接続設定は省略 -->
    <request single-result="true">
+       <set-body template="liquid"><![CDATA[
+           SELECT
+               u.[id],
+               u.[name],
+               u.[age]
+           FROM
+               users u
+           WHERE 
+               1 = 1  --(横着しました)
+       {% if body.arguments.name != null %}
+           AND u.[name] LIKE '%' + @name + '%'
+       {% endif %}
+       {% if body.arguments.age != null %}
+           AND u.[age] >= @age 
+       {% endif %}
+       ]]></set-body>
+       <sql-statement>
+           @(Regex
+               .Match(context.Request.Body.As<string>(), @"<!\[CDATA\[(?<sql>.*?)\]\]>", RegexOptions.Singleline)
+               .Groups["sql"]?.Value)
+       </sql-statement>
        <parameters>
            <parameter name="@name">@(context.GraphQL.Arguments["name"].Value<string>() ?? "empty")</parameter>
            <parameter name="@age">@(context.GraphQL.Arguments["age"].Value<int?>() ?? 0)</parameter>
        </parameters>
    </request>
</sql-data-source>

在自定义响应的部分,使用了 set-body,但在请求部分也可以使用。
因为使用不等号在 SQL 中会自动转义,所以用 然后,在 sql-statement 中引用了 set-body 设置的 body。
由于原样的话会包含

最后

通过API管理和数据库,我们能够创建GraphQL API。虽然这可能不是很方便,因为我们需要大量编写SQL语句,但反过来说也说明我们可以灵活地应对。似乎还可以调用存储过程,所以也能处理复杂的操作。调试只能查看跟踪信息,希望能变得更易于操作一些。(也许只是我不知道而已)

我也想在下一次的机会中尝试使用Cosmos DB作为数据源。

广告
将在 10 秒后关闭
bannerAds