microsoftgraph / microsoftgraph/msgraph-sdk-dotnet
Members@delta is empty during initial group delta sync under AdditionalData
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 789
- Forks
- 264
- Avg merge
- 15h 17m
- Merged PRs (30d)
- 3
Description
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_
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reproduced GraphServiceClient.Groups.Delta.GetAsDeltaGetResponseAsync calls and compare the serialized AdditionalData response between SDK 5.37 and 5.105. Trace how the initial group delta response handles members@delta, then add a regression test for the reported scenario. Done means members@delta contains the returned member objects instead of an empty object.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- api
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 45/100