15

I can query a single document from the Azure DocumentDB like this:

var response = await client.ReadDocumentAsync( documentUri );

If the document does not exist, this will throw a DocumentClientException. In my program I have a situation where the document may or may not exist. Is there any way to query for the document without using try-catch and without doing two round trips to the server, first to query for the document and second to retrieve the document should it exist?

David Makogon
  • 69,407
  • 21
  • 141
  • 189
Tuukka Haapaniemi
  • 1,156
  • 1
  • 11
  • 24

3 Answers3

10

Sadly there is no other way, either you handle the exception or you make 2 calls, if you pick the second path, here is one performance-driven way of checking for document existence:

public bool ExistsDocument(string id)
{
    var client = new DocumentClient(DatabaseUri, DatabaseKey);
    var collectionUri = UriFactory.CreateDocumentCollectionUri("dbName", "collectioName");
    var query = client.CreateDocumentQuery<Microsoft.Azure.Documents.Document>(collectionUri, new FeedOptions() { MaxItemCount = 1 });
    return query.Where(x => x.Id == id).Select(x=>x.Id).AsEnumerable().Any(); //using Linq
}

The client should be shared among all your DB-accesing methods, but I created it there to have a auto-suficient example.

The new FeedOptions () {MaxItemCount = 1} will make sure the query will be optimized for 1 result (we don't really need more).

The Select(x=>x.Id) will make sure no other data is returned, if you don't specify it and the document exists, it will query and return all it's info.

Matias Quaranta
  • 13,907
  • 1
  • 22
  • 47
9

You're specifically querying for a given document, and ReadDocumentAsync will throw that DocumentClientException when it can't find the specific document (returning a 404 in the status code). This is documented here. By catching the exception (and seeing that it's a 404), you wouldn't need two round trips.

To get around dealing with this exception, you'd need to make a query instead of a discrete read, by using CreateDocumentQuery(). Then, you'll simply get a result set you can enumerate through (even if that result set is empty). For example:

var collLink = UriFactory.CreateDocumentCollectionUri(databaseId, collectionId);
var querySpec = new SqlQuerySpec { <querytext> };

var itr = client.CreateDocumentQuery(collLink, querySpec).AsDocumentQuery();
var response = await itr.ExecuteNextAsync<Document>();

foreach (var doc in response.AsEnumerable())
{
    // ...
}

With this approach, you'll just get no responses. In your specific case, where you'll be adding a WHERE clause to query a specific document by its id, you'll either get zero results or one result.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
David Makogon
  • 69,407
  • 21
  • 141
  • 189
  • 2
    You'd probably want to handle exceptions anyway, so this seems to be a much better alternative than a preflight check for existence, IMHO. – Larry Maccherone Feb 18 '16 at 18:02
  • Handling the exception is the better approach, as in most cases querying by Id would use less RUs than a query that happens to only find one result by its Id. – Martin Costello Apr 25 '17 at 13:40
  • 9
    It is strange that we have to resort to exceptions. Communicating via exceptions usually becomes expensive as Exceptions tend to heavy and it is usually a no-no. In this case because this goes over TCP, the server needs to return a code and in this case it is a 404. So it sort of becomes a bit of a necessary evil. I like the idea of using a query instead to see if there are any results. That would not result in an exception. The code also looks weird with a try catch. – FabianVal Jul 12 '17 at 15:23
0

With CosmosDB SDK ver. 3 it's possible. You can check if an item exists in a container and get it by using Container.ReadItemStreamAsync<T>(string id, PartitionKey key) and checking response.StatusCode:

using var response = await container.ReadItemStreamAsync(id, new PartitionKey(key));

if (response.StatusCode == HttpStatusCode.NotFound)
{
    return null;
}

if (!response.IsSuccessStatusCode)
{
    throw new Exception(response.ErrorMessage);
}

using var streamReader = new StreamReader(response.Content);

var content = await streamReader.ReadToEndAsync();

var item = JsonConvert.DeserializeObject(content, stateType);

This approach has a drawback, however. You need to deserialize the item by hand.