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

C#中使用Microsoft Graph delta查询获取AAD组成员增量变更

结论

该需求可以完全通过Microsoft Graph的delta查询功能实现,无需每次全量拉取组内所有成员数据。

核心实现逻辑

delta查询的工作模式完全匹配增量跟踪需求,核心流程如下:

  • 首次执行同步时,调用目标组的传递成员delta接口拉取全量成员列表,遍历完所有分页后,会在最后一页响应中拿到@odata.deltaLink字段,你需要把这个链接持久化存储到数据库、配置中心等持久化介质中,作为后续查询增量的凭证。
  • 后续每次需要获取成员变更时,直接使用上次存储的deltaLink发起请求即可,不需要再传入组ID,接口返回的就是两次查询间隔内组内成员的所有变更。
  • 变更类型判断规则非常直接:
    • 如果返回的用户对象不存在@removed标记:本地用户库不存在该ID则为新增用户,本地已存在则为属性发生更新的用户
    • 如果返回的对象携带@removed字段,说明该用户已经被移出组(或用户本身被从租户删除),属于移除用户
  • delta查询的分页逻辑和你现有代码里处理@odata.nextLink的逻辑完全一致,只有最后一页返回的不再是nextLink,而是新的deltaLink,你需要用这个新值替换掉之前存储的旧deltaLink,供下一次增量查询使用。
适配你现有代码的参考实现

你现有的分页处理逻辑可以直接复用,只需要调整接口调用和变更判断逻辑即可,参考代码如下:

/// <summary>
/// 首次全量同步,获取全量成员+初始delta链接
/// </summary>
public async Task<(List<AzureADUser> addedOrUpdatedUsers, List<string> removedUserIds, string deltaLink)> GetInitialDeltaSyncDataAsync(string groupId)
{
    var addedOrUpdatedUsers = new List<AzureADUser>();
    var removedUserIds = new List<string>();
    
    // 发起首次delta请求
    var currentPage = await _graphServiceClient
        .Groups[groupId]
        .TransitiveMembers
        .Delta()
        .Request()
        .Top(999)
        .GetAsync();

    while (currentPage != null)
    {
        // 遍历当前页所有目录对象,过滤非用户类型
        foreach (var dirObject in currentPage.OfType<AzureADUser>())
        {
            if (dirObject.AdditionalData.ContainsKey("@removed"))
            {
                removedUserIds.Add(dirObject.Id);
            }
            else
            {
                addedOrUpdatedUsers.Add(dirObject);
            }
        }

        // 处理分页或拿到最终deltaLink
        if (currentPage.AdditionalData.TryGetValue("@odata.nextLink", out var nextLinkObj))
        {
            currentPage.InitializeNextPageRequest(_graphServiceClient, nextLinkObj.ToString());
            currentPage = await currentPage.NextPageRequest.GetAsync();
        }
        else if (currentPage.AdditionalData.TryGetValue("@odata.deltaLink", out var deltaLinkObj))
        {
            return (addedOrUpdatedUsers, removedUserIds, deltaLinkObj.ToString());
        }
        else
        {
            currentPage = null;
        }
    }

    throw new InvalidOperationException("Delta sync completed without returning a valid deltaLink");
}

/// <summary>
/// 后续增量同步,传入上次存储的deltaLink即可
/// </summary>
public async Task<(List<AzureADUser> addedOrUpdatedUsers, List<string> removedUserIds, string newDeltaLink)> GetIncrementalDeltaSyncDataAsync(string storedDeltaLink)
{
    var addedOrUpdatedUsers = new List<AzureADUser>();
    var removedUserIds = new List<string>();
    
    var currentPage = new GroupTransitiveMembersDeltaCollectionWithReferencesPage();
    currentPage.InitializeNextPageRequest(_graphServiceClient, storedDeltaLink);
    currentPage = await currentPage.NextPageRequest.GetAsync();

    while (currentPage != null)
    {
        foreach (var dirObject in currentPage.OfType<AzureADUser>())
        {
            if (dirObject.AdditionalData.ContainsKey("@removed"))
            {
                removedUserIds.Add(dirObject.Id);
            }
            else
            {
                addedOrUpdatedUsers.Add(dirObject);
            }
        }

        if (currentPage.AdditionalData.TryGetValue("@odata.nextLink", out var nextLinkObj))
        {
            currentPage.InitializeNextPageRequest(_graphServiceClient, nextLinkObj.ToString());
            currentPage = await currentPage.NextPageRequest.GetAsync();
        }
        else if (currentPage.AdditionalData.TryGetValue("@odata.deltaLink", out var newDeltaLinkObj))
        {
            return (addedOrUpdatedUsers, removedUserIds, newDeltaLinkObj.ToString());
        }
        else
        {
            currentPage = null;
        }
    }

    throw new InvalidOperationException("Delta sync completed without returning a valid deltaLink");
}
注意事项
  • 你现有代码中直接将集合结果强转为IEnumerable<AzureADUser>的写法存在运行时异常风险,因为组的传递成员可能返回嵌套组、服务主体等非用户类型的目录对象,建议加类型过滤,上面参考代码里用OfType<AzureADUser>()做了处理。
  • 存储的deltaLink如果过期、或者返回同步状态不存在的错误,需要重新走一次全量同步流程,获取新的deltaLink替换旧值即可。
  • 如果只需要跟踪用户的特定属性变更,可以在delta请求中通过$select参数指定需要返回的属性,减少不必要的传输数据量。
  • 传递成员的delta查询会自动跟踪嵌套组内的成员变更,不需要你自己递归处理嵌套组逻辑,和你现在用的TransitiveMembers接口行为一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 15:36:28