How is an IAsyncCursor used for iteration with the mongodb c# driver?

21,163

Solution 1

You have 3 options:

  1. Use the built-in driver method (e.g. ForEachAsync, ToListAsync).
  2. On C# 8.0 and above you can convert the IAsyncCursor into an IAsyncEnumerable and use await foreach or any async LINQ operator.
  3. 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)
    {
        // ...
    }
}

See: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/statements/iteration-statements#the-foreach-statement

Share:
21,163

Related videos on Youtube

Shamster
Author by

Shamster

Updated on December 31, 2021

Comments

  • Shamster
    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 strings). With the previous version of the c# driver I could call the Server.GetDatabases(), but that has been replaced with ListDatabasesAsync().

    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
    hansmaad almost 9 years
    Is it save to modiy the document in ForEachAsync and save it back to db using ReplaceOneAsync? Modifications in this case are to complex to use UpdateManyAsync
  • i3arnon
    i3arnon almost 9 years
    @hansmaad yes. It is safe. Just less efficient.
  • i3arnon
    i3arnon over 2 years
    This 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