You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Microsoft Graph API v5中结合PageIterator使用yield return

使用Graph API v5实现带yield return的IAsyncEnumerable获取组成员

问题背景

旧版Graph API SDK中,可通过NextPageRequest手动分页,结合yield return实现IAsyncEnumerable<Microsoft.Graph.User>流式返回组成员:

public async IAsyncEnumerable<Microsoft.Graph.User> LoadGroupMembers(string GroupId)
{
    if (graphClient == null) throw new ArgumentNullException(nameof(graphClient));

    var users = await graphClient.Groups[GroupId].Members
        .Request()
        .GetAsync();

    do
    {
        foreach (var member in users)
        {
            if (member is Microsoft.Graph.User user)
            {
                yield return user;
            }
        }
    }
    while (users.NextPageRequest != null && (users = await users.NextPageRequest.GetAsync()).Count > 0);
}

升级到v5版本后,SDK移除了NextPageRequest,改用PageIterator处理分页,但PageIterator的回调无法直接使用yield return,需找到替代方案实现流式返回用户数据。

解决方案

方案1:借助Channel实现异步数据传递

利用System.Threading.Channels作为异步缓冲区,在PageIterator回调中发送数据,同时从通道读取并通过yield return返回:

using System.Threading.Channels;

public async IAsyncEnumerable<Microsoft.Graph.User> LoadGroupMembers(string groupId)
{
    if (graphClient == null)
        throw new ArgumentNullException(nameof(graphClient));

    var channel = Channel.CreateUnbounded<Microsoft.Graph.User>();
    var users = await graphClient.Groups[groupId].Members.GetAsync();

    var pageIterator = PageIterator<User, UserCollectionResponse>.CreatePageIterator(
        graphClient,
        users,
        user =>
        {
            channel.Writer.TryWrite(user);
            return true;
        },
        request => request
    );

    // 后台执行分页迭代,完成后关闭通道写入端
    _ = Task.Run(async () =>
    {
        await pageIterator.IterateAsync();
        channel.Writer.Complete();
    });

    // 从通道读取数据并流式返回
    await foreach (var user in channel.Reader.ReadAllAsync())
    {
        yield return user;
    }
}

代码说明

  • Channel作为异步数据缓冲区,解决回调无法直接yield return的问题
  • 后台任务执行分页迭代,避免阻塞读取流程,迭代完成后关闭通道写入端
  • 通过await foreach读取通道数据,保持IAsyncEnumerable的流式特性

方案2:手动解析分页令牌

模拟旧版分页逻辑,直接解析OdataNextLink中的skipToken发起下一页请求:

public async IAsyncEnumerable<Microsoft.Graph.User> LoadGroupMembers(string groupId)
{
    if (graphClient == null)
        throw new ArgumentNullException(nameof(graphClient));

    string? skipToken = null;
    do
    {
        var response = await graphClient.Groups[groupId].Members
            .GetAsync(requestConfiguration =>
            {
                requestConfiguration.QueryParameters.SkipToken = skipToken;
            });

        foreach (var member in response.Value)
        {
            if (member is Microsoft.Graph.User user)
                yield return user;
        }

        skipToken = response.OdataNextLink != null ? ExtractSkipToken(response.OdataNextLink) : null;
    }
    while (skipToken != null);
}

// 从OdataNextLink中提取skipToken的辅助方法
private string? ExtractSkipToken(string nextLink)
{
    var uri = new Uri(nextLink);
    var queryParams = System.Web.HttpUtility.ParseQueryString(uri.Query);
    return queryParams["$skiptoken"];
}

代码说明

  • 直接处理分页令牌,逻辑与旧版实现一致,无需依赖PageIterator
  • 同样支持yield return流式返回用户数据

内容的提问来源于stack exchange,提问作者Richard Hubley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 22:52:57