Introduction
Delta query (also called change tracking) for driveItems
enables to discover newly created, updated, or deleted driveItems
without performing a full read of all items in OneDrive.
Using delta query helps you avoid constantly polling Microsoft Graph, as the app requests only data that changed since the last request. This pattern allows the application to reduce the amount of data it requests, thereby reducing the cost of the request and as such, likely limit the chances of the requests being throttled.
Implementing change tracking in OneDrive with the Microsoft Graph .NET SDK is not straightforward, but once you understand the concept, it is easy to implement not only for personal OneDrive.
Register Entra application
To access the Microsoft Graph API, you need to register an application in the Entra admin portal. The application will be used to authenticate the user and access personal OneDrive of signed-in user.
- Login to the Entra admin portal and register a new application.
- Add a new platform and select Mobile and desktop applications.
- Add the redirect URI
http://localhost
.
- To be able to track changes in personal OneDrive, you need to add and grant delegated permission
Files.Read
:
Go to the Overview page and copy the Application (client) ID and Directory (tenant) ID.
C# console application
Create a new C# console application (latest .NET) and install the following NuGet packages:
- Microsoft.Graph - Access the Microsoft Graph API
- Azure.Identity - Provides Entra token authentication support
Implementation
Let's say, we want to track changes in the personal OneDrive of the signed-in user for 1 hour. Create a cancellation token source and schedule cancellation after 1 hour.
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromHours(1));
Instantiate the GraphServiceClient
class and authenticate the user with the InteractiveBrowserCredential
class.
var credential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialOptions
{
ClientId = "<client_id_from_app_registration>",
TenantId = "<tenant_id>",
LoginHint = "<username>" // optional
});
var graphClient = new GraphServiceClient(credential);
Use clientId
and tenantId
from the app registration. LoginHint
is an optional parameter that specifies the username of the user to sign in. If you omit this parameter, the user will be prompted to enter their username.
Get the latest delta token
var latestTokenResponse = await graphClient.Drives["Me"].Items["Root"].DeltaWithToken("latest").GetAsDeltaWithTokenGetResponseAsync(cancellationToken: cts.Token);
var deltaLink = latestTokenResponse.OdataDeltaLink;
var timestamp = DateTime.UtcNow;
Here I'm using DeltaWithToken
method to get the latest delta token. The OdataDeltaLink
property contains the delta link that we will use to get the next set of changes.
Now, create a Task
with a while loop that will run until the cancellation token is canceled. Inside the loop, we will retrieve changes using the delta link every 10 minutes.
var trackingTask = Task.Run(async () =>
{
while (true)
{
// change delay if you want to track changes more frequently
await Task.Delay(TimeSpan.FromMinutes(10), cts.Token);
var response = await graphClient.Drives["Me"].Items["Root"].Delta.WithUrl(deltaLink).GetAsDeltaGetResponseAsync(rc =>
{
rc.Headers.Add("Prefer", "deltaExcludeParent");
}, cts.Token);
var pageIterator = PageIterator<DriveItem, DeltaGetResponse>.CreatePageIterator(graphClient, response, (item) =>
{
// ignore root item
if (item.Name == "root")
{
return true;
}
if (item.Deleted != null)
{
Console.WriteLine($"Deleted item: {item.Id}, state: {item.Deleted.State}");
return true;
}
if (item.CreatedDateTime > timestamp)
{
Console.WriteLine($"New item: {item.Id}, created: {item.CreatedDateTime}, name: {item.Name}");
return true;
}
Console.WriteLine($"Modified item: {item.Id}, modified: {item.LastModifiedDateTime}, name: {item.Name}");
return true;
},
rc =>
{
rc.Headers.Add("Prefer", "deltaExcludeParent");
return rc;
});
await pageIterator.IterateAsync(cts.Token);
// store the latest delta link and timestamp for the next iteration
deltaLink = pageIterator.Deltalink;
timestamp = DateTime.UtcNow;
}
});
If you run the console application, you will see the changes in the OneDrive of the signed-in user like this:
To apply deltaLink
, use the WithUrl(deltaLink)
method. The Prefer
header with the value deltaExcludeParent
is used to exclude the information about parent folders of changed files. If you want to include parent folders, you can remove this header. By default, the delta
endpoint includes information root of the drive, even if you specify the Prefer: deltaExcludeParent
header.
The PageIterator
class is a helper class from the Microsoft Graph .NET SDK that helps you iterate over the pages of driveItems
. The callback inside the CreatePageIterator
method is called for each driveItem
in the response. Inside the callback, you can check if the item was deleted, created, or modified. For deleted items, the Deleted
property is not null. For new items, the CreatedDateTime
property is greater than the timestamp. It's up to you to make a check for created and modified items more robust.
The IterateAsync
method is used to iterate over all pages of driveItems
.
If you want to track changes in another drive where the user has access, you can replace Me
(Drives["Me"]
) with the drive ID. In Entra admin center, you need to add and grant delegated permission Files.Read.All
to be able to track changes in other drives.
The sample application can be found in the GitHub repo