Exercise - Read and query items

Completed

Recall that your application is expected to both add items to an Azure Cosmos DB for NoSQL container and read those same items back as validation. At this point, your application successfully adds items to the container. There are two key ways to read an item: by performing a point read, or by performing a query.

There are three key requirements at this time:

  1. Point read an item using both the unique identifier and partition key value
  2. Create a query using a simple query string
  3. Paginate the results of the query using a feed iterator

Illustration of icons indicating data being queried using a query.

After you complete this exercise, your application will almost be ready. You have queries that can read the category and product items you previously created.

Point read an item

The simplest way to retrieve an item in Azure Cosmos DB is to perform a point read. Point reads use a small and predictable number of RUs as compared to queries. Here, you point read the helmets single category item you created.

  1. Return to the Program.cs file.

  2. Create a new PartitionKey instance for gear-climb-helmets.

    PartitionKey readKey = new("gear-climb-helmets");
    
  3. Use Container.ReadItemAsync to point read a specific item by using the id property and the partition key value.

    ItemResponse<Category> readResponse = await container.ReadItemAsync<Category>(
        id: "91f79374-8611-4505-9c28-3bbbf1aa7df7",
        partitionKey: readKey
    );
    
  4. Get your serialized generic type using the Resource property of the ItemResponse class.

    Category readItem = readResponse.Resource;
    
  5. Output the unique identifier and request charge for the point read operation.

    Console.WriteLine($"[Point read item]:\t{readItem.Id}\t(RUs: {readResponse.RequestCharge})");    
    
  6. Save the Program.cs file.

Execute a query

In situations where you need multiple items, you can use a query to find and retrieve those items. Recall that we used the categoryId partition key property to group our items into specific categories. If we include that property in a query, we effectively build a query scoped to a single logical partition. Now, you use a query to find all of the items in the tents category.

  1. In Program.cs, create a new string for the query SELECT * FROM products p WHERE p.categoryId = 'gear-camp-tents'. However, use a parameter named @partitionKey for the categoryId filter.

    string statement = "SELECT * FROM products p WHERE p.categoryId = @partitionKey";
    
  2. Create a new instance of the QueryDefinition class with your query string.

    var query = new QueryDefinition(
        query: statement
    );
    
  3. Use the fluent WithParameter method to assign the gear-camp-tents value to the @partitionKey parameter.

    var parameterizedQuery = query.WithParameter("@partitionKey", "gear-camp-tents");
    
  4. Use Container.GetItemQueryIterator<> to retrieve an iterator for your specific query.

    using FeedIterator<Product> feed = container.GetItemQueryIterator<Product>(
        queryDefinition: parameterizedQuery
    );
    
  5. Write the query to the console.

    Console.WriteLine($"[Start query]:\t{statement}");
    
  6. Save the Program.cs file.

Paginate query results

Azure Cosmos DB automatically breaks up your query results into pages that can be retrieved asynchronously. To manage these pages, you need to write your C# code in a specific way to ensure that you retrieve all pages of results that are available. Here, you'll use a while and foreach loop in C# to iterate over result pages.

  1. In Program.cs, create a new double variable named totalRequestCharge set to a value of 0.

    double totalRequestCharge = 0d;
    
  2. Create a while loop that iterates until the FeedIterator.HasMoreResults property of your feed iterator is false.

    while (feed.HasMoreResults)
    {
    }
    
  3. Inside of the while loop, get a new page of results using the FeedIterator.ReadNextAsync method.

    FeedResponse<Product> page = await feed.ReadNextAsync();
    
  4. Still inside of the while loop, increment the total request charge using the value of FeedResponse.RequestCharge.

    totalRequestCharge += page.RequestCharge;
    
  5. Still inside of the while loop, create a new foreach loop to iterate over the actual items in the page.

    foreach (Product item in page)
    {
    }
    
  6. Inside of the foreach loop, write to the console the id and name properties of the returned item.

    Console.WriteLine($"[Returned item]:\t{item.Id}\t(Name: {item.Name ?? "N/A"})");
    
  7. Outside of the while loop, write to the console the total request charge you calculated.

    Console.WriteLine($"[Query metrics]:\t(RUs: {totalRequestCharge})");
    

    Tip

    If you are unsure about what code should be inside or outside of the while and foreach loops, go to the review code section in Check your work.

  8. Save the Program.cs file.

Check your work

Your app now reads and queries items from the container. Here, you run the application so you can observe the results of both operations.

  1. Run the .NET application in the terminal:

    dotnet run
    
  2. Observe the output of running the application. The output should match the example here:

    ...
    [Point read item]:      91f79374-8611-4505-9c28-3bbbf1aa7df7    (RUs: 1)
    [Start query]:          SELECT * FROM products p WHERE p.categoryId = @partitionKey
    [Returned item]:        5df21ec5-813c-423e-9ee9-1a2aaead0be4    (Name: N/A)
    [Returned item]:        e8dddee4-9f43-4d15-9b08-0d7f36adcac8    (Name: Cirroa Tent)
    [Returned item]:        e6f87b8d-8cd7-4ade-a005-14d3e2fbd1aa    (Name: Kuloar Tent)
    [Returned item]:        f7653468-c4b8-47c9-97ff-451ee55f4fd5    (Name: Mammatin Tent)
    [Returned item]:        6e3b7275-57d4-4418-914d-14d1baca0979    (Name: Nimbolo Tent)
    [Query metrics]:        (RUs: 2.94)
    

    Tip

    The RUs shown in this example output may vary from your output.

    Did you notice that your category item successfully deserialized into the type you use for products? Because the category item didn't have a name property, that property was left to its default value. Type checking, schema management, and serialization/deserialization are all things your application can manage entirely client-side.