82

I am trying to make a FakeDbContext with a FakeDbSet for unit testing.

But I get the following error (see below). I am extending DbSet so normally IDbAsyncEnumerable should be implemented. And when I implement it, it says that it has no use.

Exception:

System.InvalidOperationException: The source IQueryable doesn't implement IDbAsyncEnumerable. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.

FakeDbSet class:

public abstract class FakeDbSet<TEntity> : DbSet<TEntity>, IEnumerable<TEntity>, IQueryable, IDbAsyncEnumerable<TEntity> where TEntity : Entity, new()
{
    #region Private Fields
    private readonly ObservableCollection<TEntity> _items;
    private readonly IQueryable _query;
    #endregion Private Fields

    protected FakeDbSet()
    {
        _items = new ObservableCollection<TEntity>();
        _query = _items.AsQueryable();
    }

    public Expression Expression { get { return _query.Expression; } }

    public Type ElementType { get { return _query.ElementType; } }

    public IQueryProvider Provider { get { return _query.Provider; } }

    public override TEntity Add(TEntity entity)
    {
        _items.Add(entity);
        return entity;
    }

    public override TEntity Remove(TEntity entity)
    {
        _items.Remove(entity);
        return entity;
    }

    public override TEntity Attach(TEntity entity)
    {
        switch (entity.ObjectState)
        {
            case ObjectState.Modified:
                _items.Remove(entity);
                _items.Add(entity);
                break;

            case ObjectState.Deleted:
                _items.Remove(entity);
                break;

            case ObjectState.Unchanged:
            case ObjectState.Added:
                _items.Add(entity);
                break;

            default:
                throw new ArgumentOutOfRangeException();
        }
        return entity;
    }

    public override TEntity Create() { return new TEntity(); }

    public override TDerivedEntity Create<TDerivedEntity>() { return Activator.CreateInstance<TDerivedEntity>(); }

    public override ObservableCollection<TEntity> Local { get { return _items; } }

    IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    Type IQueryable.ElementType
    {
        get { return _items.AsQueryable().ElementType; }
    }

    Expression IQueryable.Expression
    {
        get { return _items.AsQueryable().Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return _items.AsQueryable().Provider; }
    }

Here is a gist with the code. In the last file in the gist, that is where the error happens. Gist code

ataravati
  • 8,891
  • 9
  • 57
  • 89
kevingoos
  • 3,785
  • 4
  • 36
  • 63
  • Added a quick and easy way: https://stackoverflow.com/a/68103942/661933 – nawfal Jun 23 '21 at 16:41
  • 12
    Use: **using Microsoft.EntityFrameworkCore;** and remove **using System.Data.Entity;** –  Aug 17 '21 at 05:15

8 Answers8

256

In my case the exception was caused by using the wrong ToListAsync extension.

It came from:

using System.Data.Entity;

instead of

using Microsoft.EntityFrameworkCore;

Changing the namespace fixed the error.

t3chb0t
  • 16,340
  • 13
  • 78
  • 118
  • 4
    This was my issue too! For the record, I find this splitting of the nice abstraction into a concrete EFCore implementation really nasty and forces the DEV to tie into EFCore............ – James Joyce Mar 13 '19 at 02:23
  • 2
    This fixed my issue on the subject! – Aweda Jan 15 '20 at 15:53
  • 6
    This should be an accepted answer - took me ages to find this. THANKS – statler Apr 28 '20 at 08:49
  • 9
    Gets me everytime. – Scott Wilson Apr 08 '21 at 16:43
  • 5
    Googled same issue again to find that I have already upvoted your answer and that it helped me again! can't upvote twice, sorry – Yahya Hussein Aug 16 '21 at 19:44
  • 1
    @YahyaHussein happens to me over and over again with several other questions too ;-) – t3chb0t Aug 17 '21 at 05:59
  • 2
    Are these the .NET Framework and .Net Core versions respectively ? – Axel Samyn Jan 11 '22 at 16:39
  • @AxelSamyn I guess so. It's a long time ago. I think at that time both frameworks could be used interchangeably or at the same time in a single project so it was quite a chaos. Why? – t3chb0t Jan 11 '22 at 17:50
  • 2
    I'm using .NET Framework and EF 6, not Core :/ Not sure if I can find your namespace without adding references/installing new framework :'( – Axel Samyn Jan 12 '22 at 09:31
  • all of the upvotes – Mike Cole Nov 04 '22 at 21:16
  • @MikeCole they've even exploded recently. The more popular ef-core becomes, the more of an issue these namespaces seem to be. Back then I spent a couple of hours until I accidentally found this out having one project with problems and one without that helped to finally crack it. – t3chb0t Nov 05 '22 at 17:46
  • 1
    This works only for EF Core, but it does not apply to the original question as it was in .NET Framework, not Core. – Al Kepp Feb 17 '23 at 10:30
  • @AlKepp 200+ people who it helped are of a different opinion ;-] – t3chb0t Feb 17 '23 at 13:51
  • 1
    @t3chb0t Sorry, I don't understand your comment. It's a fact: This answer does not apply to .NET Framework. Those 200+ upvoters obviously use .NET Core and randomly found this page because they have the same problem. So this answer helped them, but it does not solve the original question. – Al Kepp Feb 22 '23 at 18:38
  • 1
    Upvote this for the "You got to be kidding me" reaction – MGot90 Jul 09 '23 at 22:19
  • 1
    I need to pay more attention to detail, I will do better next time – Gideon Kimutai Aug 26 '23 at 05:39
  • 1
    Can someone pls explain why the error was resolved by changing the namespace? – mohammad eunus Aug 26 '23 at 17:40
  • @mohammadeunus because there are two different extensions with the name `ToListAsync`. One of them in each namespace that extend different types. [`System.Data.Entity.QueryableExtensions.ToListAsync`](https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.queryableextensions.tolistasync?view=entity-framework-6.2.0) & there is also [`Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.tolistasync?view=efcore-7.0) – t3chb0t Aug 26 '23 at 18:00
36

Your scenario is explicitly mentioned in the link provided with the exception message (http://go.microsoft.com/fwlink/?LinkId=287068). The missing ingredient is the IDbAsyncQueryProvider that you should return from your Provider property.

Just navigate through the link to arrive at the boilerplate implementation.

Little I can add, I'll just quote the essential phrase:

In order to use asynchronous queries we need to do a little more work. If we tried to use our Moq DbSet with the GetAllBlogsAsync method we would get the following exception:

System.InvalidOperationException: The source IQueryable doesn't implement IDbAsyncEnumerable. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.

In order to use the async methods we need to create an in-memory DbAsyncQueryProvider to process the async query. Whilst it would be possible to setup a query provider using Moq, it is much easier to create a test double implementation in code. The code for this implementation is as follows:

etc...

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I did add the classes given but I don't know how I can connect it with my test. I am not using moq at the moment. I will add all my code to the question. @Hans Passant – kevingoos Oct 13 '14 at 07:29
21

I renamed the example test classes from here to remove the word Test since they are useful outside of testing:

  • DbAsyncEnumerable
  • DbAsyncEnumerator<T>
  • DbAsyncQueryProvider<TEntity>

Then I added the extension class below so you can now just do ...

var data = new List<Blog> 
{ 
    new Blog { Name = "BBB" }, 
    new Blog { Name = "ZZZ" }, 
    new Blog { Name = "AAA" }, 
}.AsAsyncQueryable();   // <<== new extension method

This is not only useful in unit tests but also when you want to implement an IQueryable<T> interface that either returns an async database query or in memory data that you can subsequently safely call as query.ToAsyncArray().

public static class AsyncQueryableExtensions
{
    public static IQueryable<TElement> AsAsyncQueryable<TElement>(this IEnumerable<TElement> source)
    {
        return new DbAsyncEnumerable<TElement>(source);
    }

    public static IDbAsyncEnumerable<TElement> AsDbAsyncEnumerable<TElement>(this IEnumerable<TElement> source)
    {
        return new DbAsyncEnumerable<TElement>(source);
    }

    public static EnumerableQuery<TElement> AsAsyncEnumerableQuery<TElement>(this IEnumerable<TElement> source)
    {
        return new DbAsyncEnumerable<TElement>(source);
    }

    public static IQueryable<TElement> AsAsyncQueryable<TElement>(this Expression expression)
    {
        return new DbAsyncEnumerable<TElement>(expression);
    }

    public static IDbAsyncEnumerable<TElement> AsDbAsyncEnumerable<TElement>(this Expression expression)
    {
        return new DbAsyncEnumerable<TElement>(expression);
    }

    public static EnumerableQuery<TElement> AsAsyncEnumerableQuery<TElement>(this Expression expression)
    {
        return new DbAsyncEnumerable<TElement>(expression);
    }
}
Tony O'Hagan
  • 21,638
  • 3
  • 67
  • 78
  • Worked a treat for me; EF 6, MSTest & NSubstitute - testing some code which called a FirstOrDefaultAsync() – Shawson Jul 03 '19 at 09:56
9

Please Change using System.Data.Entity; To using Microsoft.EntityFrameworkCore;

Masoud Maleki
  • 107
  • 1
  • 3
6

Just a note for people using the boilerplate code discussed above from Microsoft, here is a quick helper class that can transform your mocked up data into the async result. Just add to the bottom of the MS code and call with something like

var fakeDateAsMockAsyncQueryResult = new MockAsyncData<SomeType>().MockAsyncQueryResult(fakeDataList.AsQueryable());

.......

internal class MockAsyncData<T> where T : class
{ 
    public Mock<DbSet<T>> MockAsyncQueryResult(IQueryable<T> data)
    {
        var mockSet = new Mock<DbSet<T>>();

        mockSet.As<IDbAsyncEnumerable<T>>()
            .Setup(m => m.GetAsyncEnumerator())
            .Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator()));

        mockSet.As<IQueryable<T>>()
            .Setup(m => m.Provider)
            .Returns(new TestDbAsyncQueryProvider<T>(data.Provider));

        mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(data.Expression);
        mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(data.ElementType);
        mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());

        return mockSet;
    }
}

It's the same code as in the MS example but generic and reusable from many different unit tests.

Backs
  • 24,430
  • 5
  • 58
  • 85
Jodee Dex Page
  • 551
  • 5
  • 3
  • What is TestDbAsyncEnumerator and TestDbAsyncQueryProvider? – user441521 Mar 24 '17 at 19:24
  • I had the same issue as User441521. Both TestDbAsyncEnumerator and TestDbAsyncQueryProvider are undefined. – Paul Gorbas Jun 09 '17 at 16:48
  • 1
    @PaulGorbas and anyone else wondering. The missing classes *TestDbAsyncEnumerator* and *TestDbAsyncQueryProvider* are provided in the Microsoft guidance https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx. You'll need to copy the code into your own project. – Jasen Jun 23 '17 at 01:40
2

To solved my IDbAsyncEnumerable issue:

  1. Changed my project's target from .NetFramework 4.0 to .NetFramework 4.5

  2. Reinstalled EntityFramework 6.1.3 Nuget package.

  3. At this point, my Visual Studio's IDE Show Potencial Fixes advisor, allowed me to reference System.Data.Entity.Infrastructure namespace

using System.Data.Entity.Infrastructure;

Julio Nobre
  • 4,196
  • 3
  • 46
  • 49
1

DbSet probably implements IDbSet implicitly so that those methods are not available for interface mapping in your derived class.

Don't derive from IDbSet<TEntity>.

You cannot call the explicitly implemented interface members of IDbSet from a re-implementation of that interface.

Community
  • 1
  • 1
usr
  • 168,620
  • 35
  • 240
  • 369
0

Not directly related but might help some others. If you're using DelegateCompiler, make sure you're using DecompileAsync rather than Decompile in the EF packages (source).

benmccallum
  • 1,241
  • 12
  • 27