尝试在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 のリソース作成手順については説明しません。
文件中虽然写着不支持按量计费级别,但看起来现在是可用的。
(最开始我在微软的问答中询问时被告知说按量计费无法使用,所以一直搁置了。但最近他们通知我说现在可以使用了。)
准备之前
我們基本上會按照文件的指示繼續進行。
启用 管理的 API ID
这次要使用托管身份标识连接到SQL数据库,因此我们需要启用托管身份标识。在Azure门户中查看API管理资源,并通过托管身份标识将状态更改为启用。
激活 Azure AD 对 SQL 数据库的访问
为了使用托管标识连接,需要在Azure AD中启用对SQL Database的访问。请打开SQL Server资源,然后选择Azure Active Directory > 管理员设置,在其中选择您自己的帐户并点击保存。
我认为Azure Active Directory 这个词将会在将来改为Microsoft Entra ID。
分配给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 + '];
');
创建表格
建立一个合适的表格,并插入适当的数据。
创建 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
创建解析器
设定政策
连接设置(通用)
为了使用托管的标识(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会输出跟踪信息,对于调试非常有用。
给你额外的东西
自定义响应
您可以使用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作为数据源。