1

I am using MongoDB.Driver 2.10.4

I want to get all documents that have an id in a list of ids that I get from my controller

I am using this code :

var pending_processes = mongo_context.collectionName
      .AsQueryable()
      .Where(x => request.ids.Contains(x.Id))
      .Select(x => new ViewModel() 
      {
        process_id = x.Id,
        // date = not important just accessing the x object
        state = new States() 
        {
            timeline = x.states.timeline,
            part = x.states.part,
        }
      })
      .ToList();

It works fine but if I make my function async and do an await and replace ToList() with ToListAsync() I get the following error:

The source IQueryable doesn't implement IAsyncEnumerable<Application.Process.Query.GetPendingProcesses.ViewModel>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.

Clearly there is something I am not getting here my concern is I don't want my code to run synchronously this would be really bad. usually when dealing with postgresql context I always use the ToListAsync() but here in order to use linq with mongo I had to use AsQueryable() and AsQueryable() as I understand it does not get the data it's a normal query that I need to execute afterwards but when I use with it ToList() everything works but when I use ToListAsync() I get the error.

I just want to know what is behind all of this and is the code above synchronous or asynchronous?

spaleet
  • 838
  • 2
  • 10
  • 23
Charbel.AY
  • 783
  • 1
  • 11
  • 17

3 Answers3

1

I just changed my query using Find and ForEachAsync() and now all works well. I just did not use AsQueryable() because the mongodb driver as I understand it use these other functions but provides a way to use linq so I used the default methods without linq-

var result = new GetPendingProcessesViewModel();

var filter = Builders<Domain.MongoDocuments.Process>
    .Filter.Where(x => own_processes.Contains(x.Id));

await _elba_mongo_context.process
    .Find(filter)
    .ForEachAsync(x => 
            result.pending_processes_own.Add(
                    new GetPendingProcessesViewModelItem() 
                    {
                        process_id = x.Id,
                        state = new States() 
                        {
                            timeline = x.states.timeline,
                            part = x.states.part,
                            supplier = x.states.supplier
                        }
                    }
            )
    );

You can get the documentation and references for your MongoDB driver version from GitHub.

greybeard
  • 2,249
  • 8
  • 30
  • 66
Charbel.AY
  • 783
  • 1
  • 11
  • 17
0

Sure you can keep it asynchronous, but first you have switch out AsQueryable to some other method which returns back and IQueryable.

In a nutshell ToListAsync() works on a IQueryable<T> only, when you turned it in to a IEnumerable via AsEnumerable() you lost the ability to call it. Its explained well here

You have a couple of choices, either implement IDbAsyncEnumerable see here or change the result list you have into an async list with Task.FromResult()

Option 1:

  // try this in your controller
  public async Task<List<PeopleStatesType>> GetAsyncStatesList()
  {
    //for e.g. 
    List<PeopleType> peopleList = new List<PeopleType>()
    {
      new PeopleType(){ Name = "Frank", Gender = "M" },
      new PeopleType(){ Name = "Rose", Gender = "F" }  //..      
    };

    var result = from e in peopleList
                 where e.Gender == "M"
                 select e;
    return await Task.FromResult(result.ToList());
  }

Option 2: Use this class

public class AsyncEnumerableQuery<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T> {
    public AsyncEnumerableQuery(IEnumerable<T> enumerable) : base(enumerable) {
    }

    public AsyncEnumerableQuery(Expression expression) : base(expression) {
    }

    public IDbAsyncEnumerator<T> GetAsyncEnumerator() {
        return new InMemoryDbAsyncEnumerator<T>(((IEnumerable<T>) this).GetEnumerator());
    }

    IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() {
        return GetAsyncEnumerator();
    }

    private class InMemoryDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> {
        private readonly IEnumerator<T> _enumerator;

        public InMemoryDbAsyncEnumerator(IEnumerator<T> enumerator) {
            _enumerator = enumerator;
        }

        public void Dispose() {
        }

        public Task<bool> MoveNextAsync(CancellationToken cancellationToken) {
            return Task.FromResult(_enumerator.MoveNext());
        }

        public T Current => _enumerator.Current;

        object IDbAsyncEnumerator.Current => Current;
    }
}

// FindAll: with a condition like .Find(x => x.user == "Jone Doe") 
// see [here][3]
var task = collection.Find(p => true).ToListAsync();

Update Option 3: Simple Async Get

public async Task<IEnumerable<MyMongoEntity>> Where(Expression<Func<MyMongoEntity, bool>> expression = null)
{
     return await context.GetCollection<MyMongoEntity>(typeof(MyMongoEntity).Name, expression).Result.ToListAsync();
}

Based on your comment, for a simple get documents collection, this helper should work.

Transformer
  • 6,963
  • 2
  • 26
  • 52
  • Thank you for taking the time to answer this. I have seen the first option Task.FromResult but I have seen that it does not solve the problem because the query is being done synchronously but Task.FromResult only helps us await if the function is async but in the end we are still doing things synchronously as you can see here http://www.tugberkugurlu.com/archive/should-i-await-on-task-fromresult-method-calls I am just curious let's forget what I posted up there how do you query asynchronously a mongodb collection to retrieve documents having a list of documents ids in c# using linq – Charbel.AY May 11 '20 at 04:22
0

From your error, it seems like the mongo_context.collectionName is returning something from Entity Framework?

Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.

Make sure you are calling the AsQueryable extension method directly on the Mongo collection. (Your code just shows mongo_context.collectionName.AsQueryable() so I'm not sure you're doing that)

Hooking into the LINQ provider requires getting access to an IQueryable instance. The driver provides an AsQueryable extension method on IMongoCollection.

var collection = db.GetCollection<Person>("people");
var queryable = collection.AsQueryable();

Reference: https://mongodb.github.io/mongo-csharp-driver/2.10/reference/driver/crud/linq/#queryable

The AsQueryable extension above actually returns an IQueryable instance that implements IMongoQueryable and has all the same async extensions that other ORMs (Entity Framework, NHibernate, etc.) have - including ToListAsync.

crgolden
  • 4,332
  • 1
  • 22
  • 40