How is an IAsyncCursor used for iteration with the mongodb c# driver?
Solution 1
You have 3 options:
- Use the built-in driver method (e.g.
ForEachAsync
,ToListAsync
). - On C# 8.0 and above you can convert the
IAsyncCursor
into anIAsyncEnumerable
and useawait foreach
or any async LINQ operator. - Iterate over the
IAsyncCursor
.
Built-in Driver Methods
The driver has some LINQ-like extension methods for IAsyncCursor
, like AnyAsync
, ToListAsync
, etc. For iteration it has ForEachAsync
:
var cursor = await client.ListDatabasesAsync();
await cursor.ForEachAsync(db => Console.WriteLine(db["name"]));
Converting to IAsyncEnumerable
On C# 8.0 and above it's much nicer to iterate with await foreach
(and use async LINQ). This requires wrapping the IAsyncCursor
in an IAsyncEnumerable
.
You can do it yourself but since its important to get some critical things right (like cancellation and disposal) I've published a nuget package: MongoAsyncEnumerableAdapter
var cursor = await client.ListDatabasesAsync();
await foreach (var db in cursor.ToAsyncEnumerable())
{
Console.WriteLine(db["name"]);
}
Custom iteration
Traditional iteration in C# is done with IEnumerable
and foreach
. foreach
is the compiler's syntactic sugar. It's actually a call to GetEnumerator
, a using
scope and a while
loop:
using (var enumerator = enumerable.GetEnumerator())
{
while (enumerator.MoveNext())
{
var current = enumerator.Current;
// use current.
}
}
IAsyncCursor
is equivalent to IEnumerator
(the result of IEnumerable.GetEnumerator
) while IAsyncCursorSource
is to IEnumerable
. The difference is that these support async
(and get a batch each iteration and not just a single item). So you can implement the whole using
, while
loop thing yourself:
IAsyncCursorSource<int> cursorSource = null;
using (var asyncCursor = await cursorSource.ToCursorAsync())
{
while (await asyncCursor.MoveNextAsync())
{
foreach (var current in asyncCursor.Current)
{
// use current
}
}
}
Solution 2
I personally like to convert the cursor into a C# 8 IAsyncEnumerable
, that way you get all the benefits of working with enumerables (LINQ
mainly).
Using @i3arnon's "long answer" I created this extension method:
public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this IAsyncCursor<T> asyncCursor)
{
while (await asyncCursor.MoveNextAsync())
{
foreach (var current in asyncCursor.Current)
{
yield return current;
}
}
}
Solution 3
Thanks to the support of extension methods for GetAsyncEnumerator
in C# 9 and the duck-typed implementation of foreach
it is now possible to iterate the cursor directly using the following extension method:
public static class MongoDbCursorExtensions
{
public static IAsyncCursor<T> GetAsyncEnumerator<T>(this IAsyncCursor<T> cursor) => cursor;
}
Usage:
var cursor = await collection.Find(filter).ToCursorAsync();
await foreach (var batch in cursor) // extension method implicitly called here
{
foreach (var item in batch)
{
// ...
}
}
Related videos on Youtube
Shamster
Updated on December 31, 2021Comments
-
Shamster over 2 years
I'm trying to get a list of all the databases in my server and ultimately print them out (i.e. use their names as
string
s). With the previous version of the c# driver I could call theServer.GetDatabases()
, but that has been replaced withListDatabasesAsync()
.The return value is an
IAsyncCursor<>
and I'm not sure what to do with it. How does one iterate through the list of databases (or anything) with such a cursor? -
hansmaad almost 9 yearsIs it save to modiy the document in
ForEachAsync
and save it back to db usingReplaceOneAsync
? Modifications in this case are to complex to useUpdateManyAsync
-
i3arnon almost 9 years@hansmaad yes. It is safe. Just less efficient.
-
i3arnon over 2 yearsThis implementation misses some key points like cancellation and disposable (which may lead to leaks and deadlocks). I made a nuget package with a simple adapter you can use: github.com/i3arnon/MongoAsyncEnumerableAdapter