Alan2
Alan2

Reputation: 24592

Error saying "The type 'T' must be a reference type" using an Expression<Func<T, Boolean>> predicate inside a method

I am trying to change a method so it accepts a Expression<Func<T, Boolean>> predicate parameter. But when I do this it gives me an error.

Here's the old method:

    async static Task Delete() {
       var F = await cosmosManager.GetDocumentItemsAsync<LogF>(
            x => x.Activity == "F");
       await cosmosManager.DeleteDocumentItemAsync(F[0].Id);
    }

Here's the new method:

await Delete<LogF>(x => x.Activity == "F");

async static Task Delete<T>(Expression<Func<T, Boolean>> predicate) {
   var F = await cosmosManager.GetDocumentItemsAsync<T>(predicate);
   await cosmosManager.DeleteDocumentItemAsync(F[0].Id);
}

But this does not work and I get this error here: cosmosManager.GetDocumentItemsAsync<T>

Error CS0452: The type 'T' must be a reference type in order to use it as parameter 'T' in the generic type or method 'CosmosManager.GetDocumentItemsAsync(Expression>)'

Here's the code for GetDocumentItemsAsync:

    public async Task<List<T>> GetDocumentItemsAsync<T>(Expression<Func<T, Boolean>> predicate) where T : class
    {
        List<T> Items = new List<T>();
            collectionLink = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
            var query = client.CreateDocumentQuery<T>(collectionLink)
                .Where(predicate)
                .AsDocumentQuery();
            while (query.HasMoreResults)
            {
                var retrivedData1 = await query.ExecuteNextAsync<T>();
                Items.AddRange(retrivedData1);
            }
        return Items;
    }

Can someone give me a suggestion on how I could use a parameter to do the "where" part of the method and how I could call it with the parameter:

x => x.Activity == "F"

Also this just works for the first record retrieved [0]. How could I change it so that it deletes all records?

Upvotes: 1

Views: 316

Answers (2)

Caius Jard
Caius Jard

Reputation: 74680

Also this just works for the first record retrieved [0]. How could I change it so that it deletes all records?

This code appears to retrieve a collection but then only removes the first item:

var F = await cosmosManager.GetDocumentItemsAsync<T>(predicate);
await cosmosManager.DeleteDocumentItemAsync(F[0].Id);

Perhaps it would be appropriate to iterate the collection:

var F = await cosmosManager.GetDocumentItemsAsync<T>(predicate);
foreach(var f in F)
  await cosmosManager.DeleteDocumentItemAsync(f.Id);

If this is Azure CosmosDB there may be a way to speed this up by not dragging all the records to the client only to get the Id and delete one by one. I'm not sure what to advise for your particular context but I recently had to implement something to selectively delete millions of Azure Table Storage records - Batch delete in Windows Azure Table Storage was a useful resource - essentially leveraging a facility where the predicate can be sent to Azure for evaluation. I don't know if this applies/is useful for your situation

If there are only a handful of records to delete, then this simplistic approach might be ok; Jon mentions WhenAll and it would certainly be better for larger numbers of items - consider (if you have huge numbers of items and a need to keep a UI responsive) either a queuing mechanism where you mark the items for delete and some independent background process works through them, or maybe some kind of fire and forget; lot of unknowns here.

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1502756

Just apply the same constraint on your type parameter as on the one in GetDocumentItemsAsync<T>:

async static Task Delete<T>(Expression<Func<T, Boolean>> predicate) where T : class
{
    // Implementation as before
}

That will ensure that the T in your Delete method is appropriate for the T in GetDocumentItemsAsync.

You'll then have a problem that the property Id is unknown in T. Do you have some base class or interface that specifies that property? If so, add that to the constraint.

Also this just works for the first record retrieved [0]. How could I change it so that it deletes all records?

You could iterate over all the results, deleting one at a time - or use a batch delete if Cosmos provides that functionality. (I can't see anything along those lines.)

Depending on the application, you might want to parallelize this, calling DeleteDocumentItemAsync on each item first, then using Task.WhenAll to wait for all the resulting tasks to complete.

Upvotes: 3

Related Questions