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
相关产品推荐
相关产品推荐

