From c0b6900cb24207386bdd537dd3a7f7c28815c5be Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Wed, 9 Apr 2025 15:15:23 -0400 Subject: [PATCH 1/6] DOCSP-49067: Cursors --- source/crud/query/cursors.txt | 177 ++++++++++++++++++ .../fundamentals/code-examples/Cursor.cs | 154 +++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 source/includes/fundamentals/code-examples/Cursor.cs diff --git a/source/crud/query/cursors.txt b/source/crud/query/cursors.txt index e69de29b..8d330d1c 100644 --- a/source/crud/query/cursors.txt +++ b/source/crud/query/cursors.txt @@ -0,0 +1,177 @@ +.. _csharp-cursors: + +========================= +Access Data From a Cursor +========================= + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +.. facet:: + :name: genre + :values: reference + +.. meta:: + :keywords: read, results, oplog + +Overview +-------- + +In this guide, you can learn how to access data from a **cursor** by using the +{+driver-short+}. + +A cursor is a mechanism that returns the results of a read operation in iterable +batches. Because a cursor holds only a subset of documents at any given time, +cursors reduce both memory consumption and network bandwidth usage. + +You can retrieve a cursor by using the ``FindSync()`` and ``FindAsync()`` methods, +or by using the ``Find.ToCursor()`` and ``Find.ToCursorAsync()`` methods. + +Sample Data +~~~~~~~~~~~ + +The examples in this guide use the ``restaurants`` collection +in the ``sample_restaurants`` database provided in the :atlas:`Atlas sample datasets `. +To learn how to create a free MongoDB Atlas cluster and load the sample datasets, +see the :ref:`` tutorial. + +The examples on this page use the following ``Restaurant`` object to model the documents +in the ``restaurants`` collection: + +.. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-restaurant-class + :end-before: end-restaurant-class + :language: csharp + +.. _csharp-cursors-iterate: + +Access Cursor Contents Iteratively +---------------------------------- + +To iterate over the contents of a cursor, use a ``foreach`` loop inside a ``using`` block. +The following example retrieves all documents from the ``restaurants`` collection and +iterates over the results. Select the :guilabel:`Synchronous` or :guilabel:`Asynchronous` +tab to see the corresponding code: + +.. tabs:: + + .. tab:: Synchronous + :tabid: sync + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-cursor-iterate + :end-before: end-cursor-iterate + :language: csharp + :dedent: + + .. tab:: Asynchronous + :tabid: async + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-cursor-iterate-async + :end-before: end-cursor-iterate-async + :language: csharp + :dedent: + +The following example performs the same operation but uses the ``ToCursor()`` method. +Select the :guilabel:`Synchronous` or :guilabel:`Asynchronous` +tab to see the corresponding code: + +.. tabs:: + + .. tab:: Synchronous + :tabid: sync + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-cursor-iterate-to-cursor + :end-before: end-cursor-iterate-to-cursor + :language: csharp + :dedent: + + .. tab:: Asynchronous + :tabid: async + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-cursor-iterate-to-cursor-async + :end-before: end-cursor-iterate-to-cursor-async + :language: csharp + :dedent: + +Retrieve All Documents +---------------------- + +.. warning:: + + If the number and size of documents returned by your query exceeds available + application memory, your program will crash. If you expect a large result + set, :ref:`access your cursor iteratively `. + +To retrieve all documents from a cursor, use the ``ToList()`` method, as shown in the +following example. Select the :guilabel:`Synchronous` or :guilabel:`Asynchronous` +tab to see the corresponding code: + +.. tabs:: + + .. tab:: Synchronous + :tabid: sync + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-cursor-to-list + :end-before: end-cursor-to-list + :language: csharp + :dedent: + + .. tab:: Asynchronous + :tabid: async + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-cursor-to-list-async + :end-before: end-cursor-to-list-async + :language: csharp + :dedent: + +Tailable Cursors +---------------- + +When querying on a :manual:`capped collection `, you +can use a **tailable cursor** that remains open after the client exhausts the +results in a cursor. To create a tailable cursor with capped collection, +set the ``CursorType`` option of a ``FindOptions`` object to ``CursorType.TailableAwait`` +and pass this ``FindOptions`` object to the find method of your choice. The following example +shows how to create a tailable cursor on a capped collection. Select the :guilabel:`Synchronous` +or :guilabel:`Asynchronous` tab to see the corresponding code: + +.. tabs:: + + .. tab:: Synchronous + :tabid: sync + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-tailable-cursor + :end-before: end-tailable-cursor + :language: csharp + :dedent: + + .. tab:: Asynchronous + :tabid: async + + .. literalinclude:: /includes/fundamentals/code-examples/Cursor.cs + :start-after: start-tailable-cursor-async + :end-before: end-tailable-cursor-async + :language: csharp + :dedent: + +API Documentation +----------------- + +To learn more about the methods and classes used in this guide, see the +following API documentation: + +- `FindSync() <{+new-api-root+}/MongoDB.Driver/MongoDB.Driver.IMongoCollection-1.FindSync.html>`__ +- `FindAsync() <{+new-api-root+}/MongoDB.Driver/MongoDB.Driver.IMongoCollection-1.FindAsync.html>`__ +- `Find() <{+new-api-root+}/MongoDB.Driver/MongoDB.Driver.IMongoCollectionExtensions.Find.html>`__ +- `IAsyncCursor <{+new-api-root+}/MongoDB.Driver/MongoDB.Driver.IAsyncCursor-1.html>`__ +- `FindOptions <{+new-api-root+}/MongoDB.Driver/MongoDB.Driver.FindOptions.html>`__ diff --git a/source/includes/fundamentals/code-examples/Cursor.cs b/source/includes/fundamentals/code-examples/Cursor.cs new file mode 100644 index 00000000..c5fe47fa --- /dev/null +++ b/source/includes/fundamentals/code-examples/Cursor.cs @@ -0,0 +1,154 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; + +public class Cursor +{ + // Replace with your connection string + private const string MongoConnectionString = ""; + + public static void Main(string[] args) + { + var mongoClient = new MongoClient(MongoConnectionString); + var database = mongoClient.GetDatabase("sample_restaurants"); + var collection = database.GetCollection("restaurants"); + + { + // start-cursor-iterate + var filter = Builders.Filter.Empty; + + using (var cursor = collection.FindSync(filter)) + { + while (cursor.MoveNext()) + { + foreach (var restaurant in cursor.Current) + { + Console.WriteLine(restaurant.Name); + } + } + } + // end-cursor-iterate + } + + { + // start-cursor-iterate-async + var filter = Builders.Filter.Empty; + + using (var cursor = await collection.FindAsync(filter)) + { + while (await cursor.MoveNextAsync()) + { + foreach (var restaurant in cursor.Current) + { + Console.WriteLine(restaurant.Name); + } + } + } + // end-cursor-iterate-async + } + + { + // start-cursor-iterate-to-cursor + var filter = Builders.Filter.Empty; + + using (var cursor = collection.Find(filter).ToCursor()) + { + while (cursor.MoveNext()) + { + foreach (var restaurant in cursor.Current) + { + Console.WriteLine(restaurant.Name); + } + } + } + // end-cursor-iterate-to-cursor + } + + { + // start-cursor-iterate-to-cursor-async + var filter = Builders.Filter.Empty; + + using (var cursor = await collection.Find(filter).ToCursorAsync()) + { + while (await cursor.MoveNextAsync()) + { + foreach (var restaurant in cursor.Current) + { + Console.WriteLine(restaurant.Name); + } + } + } + // end-cursor-iterate-to-cursor-async + } + + { + // start-cursor-to-list + var filter = Builders.Filter.Eq(r => r.Name, "Dunkin' Donuts"); + var results = collection.FindSync(filter).ToList(); + // end-cursor-to-list + } + + { + // start-cursor-to-list-async + var filter = Builders.Filter.Eq(r => r.Name, "Dunkin' Donuts"); + var results = (await collection.FindAsync(filter)).ToList(); + // end-cursor-to-list-async + } + + { + // start-tailable-cursor + var filter = Builders.Filter.Eq(r => r.Name, "Dunkin' Donuts"); + var options = new FindOptions + { + CursorType = CursorType.TailableAwait + }; + + using (var cursor = collection.FindSync(filter, options)) + { + while (cursor.MoveNext()) + { + foreach (var restaurant in cursor.Current) + { + // Process each restaurant + } + } + } + // end-tailable-cursor + } + + { + { + // start-tailable-cursor-async + var filter = Builders.Filter.Eq(r => r.Name, "Dunkin' Donuts"); + var options = new FindOptions + { + CursorType = CursorType.TailableAwait + }; + + using (var cursor = await collection.FindAsync(filter, options)) + { + while (cursor.MoveNext()) + { + foreach (var restaurant in cursor.Current) + { + // Process each restaurant + } + } + } + // end-tailable-cursor-async + } + } + + } +} + +// start-restaurant-class +[BsonIgnoreExtraElements] +public class Restaurant +{ + public ObjectId Id { get; set; } + + [BsonElement("name")] + public string Name { get; set; } +} +// end-restaurant-class \ No newline at end of file From f7174cfd4bb4ad7dfbfcb1c5191fe1f342cb62a4 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Wed, 9 Apr 2025 15:26:48 -0400 Subject: [PATCH 2/6] Fixes --- source/crud/query/cursors.txt | 5 +++-- source/includes/fundamentals/code-examples/Cursor.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/crud/query/cursors.txt b/source/crud/query/cursors.txt index 8d330d1c..495764a3 100644 --- a/source/crud/query/cursors.txt +++ b/source/crud/query/cursors.txt @@ -27,8 +27,9 @@ A cursor is a mechanism that returns the results of a read operation in iterable batches. Because a cursor holds only a subset of documents at any given time, cursors reduce both memory consumption and network bandwidth usage. -You can retrieve a cursor by using the ``FindSync()`` and ``FindAsync()`` methods, -or by using the ``Find.ToCursor()`` and ``Find.ToCursorAsync()`` methods. +You can retrieve a cursor by using the ``FindSync()`` and ``FindAsync()`` methods. You can +also convert the results of the ``Find()`` method to a cursor by chaining the ``ToCursor()`` +or ``ToCursorAsync()`` method. Sample Data ~~~~~~~~~~~ diff --git a/source/includes/fundamentals/code-examples/Cursor.cs b/source/includes/fundamentals/code-examples/Cursor.cs index c5fe47fa..a22105d5 100644 --- a/source/includes/fundamentals/code-examples/Cursor.cs +++ b/source/includes/fundamentals/code-examples/Cursor.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; @@ -7,7 +8,7 @@ public class Cursor // Replace with your connection string private const string MongoConnectionString = ""; - public static void Main(string[] args) + public static async Task Main(string[] args) { var mongoClient = new MongoClient(MongoConnectionString); var database = mongoClient.GetDatabase("sample_restaurants"); @@ -127,7 +128,7 @@ public static void Main(string[] args) using (var cursor = await collection.FindAsync(filter, options)) { - while (cursor.MoveNext()) + while (await cursor.MoveNext()) { foreach (var restaurant in cursor.Current) { From 2e5deb73acaa408c555066258c62466b5655b32a Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Wed, 9 Apr 2025 15:27:53 -0400 Subject: [PATCH 3/6] Fix --- source/crud/query/cursors.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/crud/query/cursors.txt b/source/crud/query/cursors.txt index 495764a3..ac7248aa 100644 --- a/source/crud/query/cursors.txt +++ b/source/crud/query/cursors.txt @@ -52,7 +52,7 @@ in the ``restaurants`` collection: Access Cursor Contents Iteratively ---------------------------------- -To iterate over the contents of a cursor, use a ``foreach`` loop inside a ``using`` block. +To iterate over the contents of a cursor, use a ``foreach`` loop inside of a ``using`` block. The following example retrieves all documents from the ``restaurants`` collection and iterates over the results. Select the :guilabel:`Synchronous` or :guilabel:`Asynchronous` tab to see the corresponding code: From cecf51f117855694b405bc045bb1c3cc26c4a72a Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Thu, 10 Apr 2025 09:22:50 -0400 Subject: [PATCH 4/6] Fix --- source/crud/query/cursors.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/crud/query/cursors.txt b/source/crud/query/cursors.txt index ac7248aa..495764a3 100644 --- a/source/crud/query/cursors.txt +++ b/source/crud/query/cursors.txt @@ -52,7 +52,7 @@ in the ``restaurants`` collection: Access Cursor Contents Iteratively ---------------------------------- -To iterate over the contents of a cursor, use a ``foreach`` loop inside of a ``using`` block. +To iterate over the contents of a cursor, use a ``foreach`` loop inside a ``using`` block. The following example retrieves all documents from the ``restaurants`` collection and iterates over the results. Select the :guilabel:`Synchronous` or :guilabel:`Asynchronous` tab to see the corresponding code: From 81afedcb1a6abe3a4ddac2c98c764605eb41f0ac Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Thu, 10 Apr 2025 10:49:46 -0400 Subject: [PATCH 5/6] RR feedback --- source/crud/query/cursors.txt | 16 ++++++++-------- .../fundamentals/code-examples/Cursor.cs | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/source/crud/query/cursors.txt b/source/crud/query/cursors.txt index 495764a3..96318ab4 100644 --- a/source/crud/query/cursors.txt +++ b/source/crud/query/cursors.txt @@ -23,11 +23,11 @@ Overview In this guide, you can learn how to access data from a **cursor** by using the {+driver-short+}. -A cursor is a mechanism that returns the results of a read operation in iterable +A cursor is a tool that returns the results of a read operation in iterable batches. Because a cursor holds only a subset of documents at any given time, cursors reduce both memory consumption and network bandwidth usage. -You can retrieve a cursor by using the ``FindSync()`` and ``FindAsync()`` methods. You can +You can retrieve a cursor by using the ``FindSync()`` or ``FindAsync()`` method. You can also convert the results of the ``Find()`` method to a cursor by chaining the ``ToCursor()`` or ``ToCursorAsync()`` method. @@ -107,7 +107,7 @@ Retrieve All Documents .. warning:: If the number and size of documents returned by your query exceeds available - application memory, your program will crash. If you expect a large result + application memory, your program might crash. If you expect a large result set, :ref:`access your cursor iteratively `. To retrieve all documents from a cursor, use the ``ToList()`` method, as shown in the @@ -139,11 +139,11 @@ Tailable Cursors When querying on a :manual:`capped collection `, you can use a **tailable cursor** that remains open after the client exhausts the -results in a cursor. To create a tailable cursor with capped collection, -set the ``CursorType`` option of a ``FindOptions`` object to ``CursorType.TailableAwait`` -and pass this ``FindOptions`` object to the find method of your choice. The following example -shows how to create a tailable cursor on a capped collection. Select the :guilabel:`Synchronous` -or :guilabel:`Asynchronous` tab to see the corresponding code: +results in a cursor. To create a tailable cursor, create a ``FindOptions`` object and set the +``CursorType`` property to ``CursorType.TailableAwait``. Then, pass the ``FindOptions`` object +to one of the find operation methods. The following example shows how to create a tailable +cursor on a capped collection. Select the :guilabel:`Synchronous` or +:guilabel:`Asynchronous` tab to see the corresponding code: .. tabs:: diff --git a/source/includes/fundamentals/code-examples/Cursor.cs b/source/includes/fundamentals/code-examples/Cursor.cs index a22105d5..c8de98a2 100644 --- a/source/includes/fundamentals/code-examples/Cursor.cs +++ b/source/includes/fundamentals/code-examples/Cursor.cs @@ -16,7 +16,7 @@ public static async Task Main(string[] args) { // start-cursor-iterate - var filter = Builders.Filter.Empty; + var filter = Builders.Filter.Eq(r => r.Name, "Starbucks"); using (var cursor = collection.FindSync(filter)) { @@ -33,7 +33,7 @@ public static async Task Main(string[] args) { // start-cursor-iterate-async - var filter = Builders.Filter.Empty; + var filter = Builders.Filter.Eq(r => r.Name, "Starbucks"); using (var cursor = await collection.FindAsync(filter)) { @@ -50,7 +50,7 @@ public static async Task Main(string[] args) { // start-cursor-iterate-to-cursor - var filter = Builders.Filter.Empty; + var filter = Builders.Filter.Eq(r => r.Name, "Starbucks"); using (var cursor = collection.Find(filter).ToCursor()) { @@ -67,7 +67,7 @@ public static async Task Main(string[] args) { // start-cursor-iterate-to-cursor-async - var filter = Builders.Filter.Empty; + var filter = Builders.Filter.Eq(r => r.Name, "Starbucks"); using (var cursor = await collection.Find(filter).ToCursorAsync()) { @@ -110,7 +110,7 @@ public static async Task Main(string[] args) { foreach (var restaurant in cursor.Current) { - // Process each restaurant + Console.WriteLine(restaurant.Name); } } } @@ -132,7 +132,7 @@ public static async Task Main(string[] args) { foreach (var restaurant in cursor.Current) { - // Process each restaurant + Console.WriteLine(restaurant.Name); } } } From 43f9d813d6b631ad305966d3bbcdd29a203ef5d2 Mon Sep 17 00:00:00 2001 From: Michael Morisi Date: Wed, 16 Apr 2025 09:04:13 -0400 Subject: [PATCH 6/6] RR feedback 2 --- source/crud/query/cursors.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/crud/query/cursors.txt b/source/crud/query/cursors.txt index 96318ab4..a3307199 100644 --- a/source/crud/query/cursors.txt +++ b/source/crud/query/cursors.txt @@ -53,9 +53,10 @@ Access Cursor Contents Iteratively ---------------------------------- To iterate over the contents of a cursor, use a ``foreach`` loop inside a ``using`` block. -The following example retrieves all documents from the ``restaurants`` collection and -iterates over the results. Select the :guilabel:`Synchronous` or :guilabel:`Asynchronous` -tab to see the corresponding code: +The following example retrieves documents from the ``restaurants`` collection in which the +value of the ``name`` field is ``"Starbucks"``, then iterates over the results. +Select the :guilabel:`Synchronous` or :guilabel:`Asynchronous` tab to see the corresponding +code: .. tabs::