Skip to content

Members@delta is empty during initial group delta sync under AdditionalData #3102

@Vijaymahantesh-bit

Description

@Vijaymahantesh-bit

Describe the bug

After updating the MS Graph SDK from 5.37 to 5.105. Members@delta is empty during initial group delta sync. Below is the code snippet & response attached for both versions.

Expected behavior

Expecting members delta under Additional data as shown
"AdditionalData": { "members@delta": [ { "@odata.type": "#microsoft.graph.user", "id": "<Guid>" }, { "@odata.type": "#microsoft.graph.user", "id": "<Guid>" }, { "@odata.type": "#microsoft.graph.user", "id": "<Guid>" } ] },

with latest SDK getting the empty response as show below
"AdditionalData": { "members@delta": {} },

How to reproduce

Code used to reproduce.

private static async Task DebugGroupMembersSyncWorkflow(GraphServiceClient client, string groupId = null!, string deltaLink = null!)
    {
        var nextLink = deltaLink;
        do
        {
            var deltaResponse = string.IsNullOrEmpty(nextLink)
                ? await GetDeltaResponse(client, groupId).ConfigureAwait(false)
                : await GetDeltaResponse(client, new Uri(nextLink)).ConfigureAwait(false);

            nextLink = deltaResponse.NextLink;
            deltaLink = deltaResponse.DeltaLink;

            if (!string.IsNullOrEmpty(nextLink))
            {
                var memberCount = deltaResponse.MemberIds.Count();
                var removedCount = deltaResponse.RemovedMemberIds.Count();
                var hasNextLink = !string.IsNullOrEmpty(deltaResponse.NextLink);

                Console.WriteLine($"MemberIds count: {memberCount}, RemovedMemberIds count: {removedCount}, HasNextLink: {hasNextLink}");
            }

            Console.WriteLine($"HasNextLink: {!string.IsNullOrEmpty(deltaResponse.NextLink)}, HasDeltaLink: {!string.IsNullOrEmpty(deltaResponse.DeltaLink)}");
            
        } while (!string.IsNullOrEmpty(nextLink));

        Console.WriteLine($"Completed group members delta sync and has DeltaSyncLink {!string.IsNullOrEmpty(deltaLink)}");
        Console.WriteLine("Would you want to run delta sync again using the delta link? (y/n): ");
        var input = Console.ReadLine();

        if(input == "y")
        {
            await DebugGroupMembersSyncWorkflow(client, deltaLink: deltaLink);
        }
    }

    private static async Task<GroupMembersDeltaResponse> GetDeltaResponse(GraphServiceClient client, string groupId)
    {
        var deltaResponse = await client.Groups.Delta.GetAsDeltaGetResponseAsync(
            config =>
            {
                config.QueryParameters.Filter = $"id eq '{groupId}'";
                config.QueryParameters.Select = ["displayName", "members"];
            },
            CancellationToken.None).ConfigureAwait(false);

        string serlializedResponse = System.Text.Json.JsonSerializer.Serialize(deltaResponse);

        return deltaResponse!.GetGroupMembersDelta();
    }

    private static async Task<GroupMembersDeltaResponse> GetDeltaResponse(GraphServiceClient client, Uri url)
    {
        var deltaResponse = await client.Groups.Delta
                                .WithUrl(url.ToString())
                                .GetAsDeltaGetResponseAsync(cancellationToken: CancellationToken.None).ConfigureAwait(false);

        ArgumentNullException.ThrowIfNull(deltaResponse, nameof(deltaResponse));

        return deltaResponse.GetGroupMembersDelta();
    }

    private static GroupMembersDeltaResponse GetGroupMembersDelta(this Microsoft.Graph.Groups.Delta.DeltaGetResponse deltaGetResponse)
    {
        ArgumentNullException.ThrowIfNull(deltaGetResponse, nameof(deltaGetResponse));

        var group = deltaGetResponse.Value?.FirstOrDefault();
        if (group == null)
        {
            return new GroupMembersDeltaResponse
            {
                NextLink = deltaGetResponse.OdataNextLink ?? string.Empty,
                DeltaLink = deltaGetResponse.OdataDeltaLink ?? string.Empty,
            };
        }

        var groupMembersDeltaResponse = new GroupMembersDeltaResponse
        {
            GroupId = group.Id!,
            GroupName = group.DisplayName!,
            MemberIds = Enumerable.Empty<string>(),
            RemovedMemberIds = Enumerable.Empty<string>(),
            NextLink = deltaGetResponse.OdataNextLink!,
            DeltaLink = deltaGetResponse.OdataDeltaLink!,
        };

        if (group.AdditionalData.TryGetValue("members@delta", out var membersDelta) && membersDelta is JsonElement membersDeltaElement)
        {
            var members = System.Text.Json.JsonSerializer.Deserialize<IEnumerable<GraphUserRef>>(membersDeltaElement.GetRawText());
            groupMembersDeltaResponse.MemberIds = members?.Where(x => x.Removed == null).Select(x => x.Id) ?? Enumerable.Empty<string>();
            groupMembersDeltaResponse.RemovedMemberIds = members?.Where(x => x.Removed != null).Select(x => x.Id) ?? Enumerable.Empty<string>();
        }

        return groupMembersDeltaResponse;
    }
}


public record GraphUserRef(
    [JsonPropertyName("@odata.type")] string ODataType,
    [JsonPropertyName("id")] string Id,
    [JsonPropertyName("@removed")] string? Removed
);

public class GroupMembersDeltaResponse
{
    /// <summary>
    /// Gets or sets the unique identifier of the group.
    /// </summary>
    public string GroupId { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the name of the group.
    /// </summary>
    public string GroupName { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the member IDs of the group.
    /// </summary>
    public IEnumerable<string> MemberIds { get; set; } = Array.Empty<string>();

    /// <summary>
    /// Gets or sets the removed member IDs of the group.
    /// </summary>
    public IEnumerable<string> RemovedMemberIds { get; set; } = Array.Empty<string>();

    /// <summary>
    /// Gets or sets the next link for pagination.
    /// </summary>
    public string NextLink { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the URL that can be used to retrieve changes since the last request.
    /// </summary>
    public string DeltaLink { get; set; } = string.Empty;
}

SDK Version

5.105

Latest version known to work for scenario above?

5.37

Known Workarounds

Downgrading the package works for now.

Debug output

Click to expand log ```
</details>


### Configuration

_No response_

### Other information

_No response_

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions