What do I replace IQueryable with for Async operations?

c# entity-framework-core linq

Question

Let's say I have a Projects table in my DB with multiple relationships. Various different queries in the DB layer of my c# code would be interested in joining this table with different others. So I abstracted away the following:

public IQueryable<Project> QueryProject(Project prj)
{
    return Context.Projects.Where(p => p.id == prj.id);
}

... so I can reuse this method in various other scenarios, such as...

_queryService.QueryProject(prj).Include(p => p.Users)

... or

_queryService.QueryProject(prj).Include(p => p.Artefacts)

... etc. At one point, I decide that making action methods asynchronous is a good idea, so I try the following:

await _queryService.QueryProject(prj).Include(p => p.Artefacts).SingleAsync();

However, the moment I try to make any operation using the QueryProject method asynchronous, I get the following error from my unit tests:

The source IQueryable doesn't implement IAsyncEnumerable. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.

So clearly I shouldn't be using IQueryable as a return type for my first QueryProject abstraction. However, I'm struggling to find a viable alternative. I do NOT want any DB retrieval of entities during the execution of QueryProject. I'm also not interested in hacks that simply hide this error without bringing me any benefit. I'm only trying to abstract away repeating code.

Is some type of alternative supported in .NET core 2.1?

P.S. Ivan Stoev raised a valid concern, that the problem could be in my unit tests. Here's my mocking code for reference (using Moq):

mockServices.Setup(x => x.QueryProject(It.IsAny<SomeProjectAbstraction>()))
    .Returns(new List<Project>
    {
        new Project
        {
            Name = ...
        }
    }.AsQueryable());
1
2
8/13/2018 1:44:03 PM

Expert Answer

This is because of your mocking approach; your mock provider just returns panels for Query, and panels is a simple object with LINQ-to-Objects exposing it as queryable:

private readonly IQueryable<Panel> panels = new List<Panel>() { panel }.AsQueryable();

Indeed, this does not implement IAsyncQueryProvider. If you can get hold of the regular query provider, you should be able to wrap that with a fake always-synchronous version to spoof it (just use return Task.FromResult(Execute(expression))), but frankly I'm not sure that this would be a useful test... at that point you're skipping so many of the important realities of async that it probably isn't worth it.

4
6/25/2018 12:34:27 PM

Popular Answer

I get stuck on this issue today and this lib resolve it for me https://github.com/romantitov/MockQueryable completely, please refer:

Moking Entity Framework Core operations such ToListAsync, FirstOrDefaultAsync etc

//1 - create a List<T> with test items
var users = new List<UserEntity>()
{
  new UserEntity{LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")},
  ...
};

//2 - build mock by extension
var mock = users.AsQueryable().BuildMock();

//3 - setup the mock as Queryable for Moq
_userRepository.Setup(x => x.GetQueryable()).Returns(mock.Object);

//3 - setup the mock as Queryable for NSubstitute
_userRepository.GetQueryable().Returns(mock);


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow