3

Following code, runs trough the list and removes each item. Is there any "nicer" way to delete all items from a list? (Except of removing it and recreating it?) Something like List.Purge

var deleteQueryOptions = new List<QueryOption>()
{
   new QueryOption("expand", "fields(select=id)")
};

Console.WriteLine("Deleting ROWS from the list (UPDATING)");
var deleteItems = await graphServiceClient.Sites[siteUrl].Lists[listName].Items
                  .Request(deleteQueryOptions)
                  .GetAsync();

do
{
   Console.ForegroundColor = ConsoleColor.DarkYellow;
   Console.WriteLine("Processing PAGE of LIST ITEMS");
   Console.ResetColor();

   foreach (var deleteItem in deleteItems)
   {
      await graphServiceClient.Sites[siteUrl].Lists[listName].Items[deleteItem.Id]
                                .Request()
                                .DeleteAsync();
   }

   Console.ForegroundColor = ConsoleColor.Green;
   Console.WriteLine("Another PAGE of List Items successfully deleted");
   Console.ResetColor();
   try
   {
     deleteItems = await deleteItems.NextPageRequest.GetAsync();
   }
   catch
   {
     Console.ForegroundColor = ConsoleColor.DarkYellow;
     Console.WriteLine("There is no NextPageRequest for deleting items.");
     Console.ResetColor();
   }
   // While there is another page with data, get it and do the process again (At this moment 1 page contains 200 items, written by Marek Kyzivat @ 05/08/2019 )
} while (deleteItems.NextPageRequest != null);

This gets the job done, but once you have a bigger list it can take some time.

Marek Kyzivát
  • 323
  • 2
  • 16
  • If instead of doing await on the task, you add the task to an array and then do Task.WaitAll you can make a bunch of calls in parallel instead of waiting for each one to complete before calling the next. – Darrel Miller Sep 25 '19 at 00:50
  • oh cool, thanks for advice, also do you have an example? :) – Marek Kyzivát Sep 25 '19 at 09:57
  • [Like this?](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitall?view=netframework-4.8) – Marek Kyzivát Sep 25 '19 at 12:04
  • The recommended way to manage SharePoint content en masse is to use PowerShell. – TylerH Sep 25 '19 at 15:48
  • @TylerH Recommended by who? – Darrel Miller Sep 26 '19 at 16:44
  • @DarrelMiller Microsoft. – TylerH Sep 26 '19 at 18:49
  • @TylerH If you could point me to that guidance, that would be great. Sounds like it could use some updating. There are many customers who are using the Graph SDKs to do a high volume of calls against Microsoft Graph. – Darrel Miller Sep 26 '19 at 19:31
  • @DarrelMiller I don't have anything specific off the top of my head but have seen countless pages on docs.microsoft.com, technet, etc. for administrating/managing SharePoint Online where there are parts that say "you'll have to use PowerShell to do this" or "you can easily do this by running these things in PowerShell". Countless other quasi-official sources like MVPs (Shane Young, et al) also mention it. If Graph can do stuff that used to require PowerShell, that sounds great (I wouldn't know; I've never used it), and I agree you should ask the relevant cohorts to update the guidance in Docs. – TylerH Sep 26 '19 at 19:42
  • @TylerH Agreed there are definitely areas where there are gaps in the Microsoft Graph surface area and often Powershell is the fallback. – Darrel Miller Sep 26 '19 at 21:56

2 Answers2

1

If you update your inner loop like this, it should help.

ServicePointManager.DefaultConnectionLimit = 30;
var tasks = new List<Task>();

foreach (var deleteItem in deleteItems)
{
    task.Add(graphServiceClient.Sites[siteUrl].Lists[listName].Items[deleteItem.Id]
                                .Request()
                                .DeleteAsync());
}

Task.WaitAll(tasks.ToArray());

Also, note that the default number of open connections is 2. Increasing this number will use a bit more memory but it will allow many more concurrent calls.

I would be really interested in knowing the performance impact in this particular situation. We are currently trying to identify better programming models for making concurrent requests to Graph.

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • Deleting using Task.WaitAll seems to work fine, but creating items seems to have an issue. Is it possible that there is some inner SharePoint issue where multiple tasks try to populate same "id" position when being called at once? – Marek Kyzivát Sep 26 '19 at 08:37
  • 1
    @MarekKyzivát There may be some concurrency limits on creating list items. You may want to limit the number of parallel tasks for creating items. Or alternatively you could try using the BatchRequestContent object to create up to 20 items in a single request. – Darrel Miller Sep 26 '19 at 16:46
  • I have limited the number of parallel tasks right away as I typed the comment. Seems to be ok for now, but will definitely try the BatchRequestContent as well. Thank you very much Darrel for your suggestions and help :-) – Marek Kyzivát Sep 29 '19 at 08:07
0

Here is a solution using Batching which deletes 20 records at a time. Optionally we can pass a filter that deletes only a subset of items

public async Task DeleteListItems(string siteId, string listId, string filterString = "")
{
    List<QueryOption> queryOptions = new List<QueryOption>();
    queryOptions.Add(new QueryOption("select", "id"));
    if (!string.IsNullOrEmpty(filterString))
    {
        queryOptions.Add(new QueryOption("filter", filterString));
    }

    var response = await graphServiceClient.Sites[siteId].Lists[listId].Items.Request(queryOptions).GetAsync();

    foreach (var chunkedItems in response.ToList().Chunk(20))
    {
        var batchRequestContent = new BatchRequestContent();

        foreach (var chunkedItem in chunkedItems)
        {
            var deleteItemRequest = graphServiceClient.Sites[siteId].Lists[listId].Items[chunkedItem.Id].Request().GetHttpRequestMessage();
            deleteItemRequest.Method = HttpMethod.Delete;
            batchRequestContent.AddBatchRequestStep(deleteItemRequest);
        }
        await graphServiceClient.Batch.Request().PostAsync(batchRequestContent);
    }
}