尝试在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作为数据源。