Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AutoMapper unable to cast TestDbAsyncEnumerable to IQueryable

I've implemented the TestDbAsync fakes from https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx and I want to be able to use AutoMapper to project to a different type before calling the Async EF methods (ToListAsync, CountAsync, etc.).

I get a cast exception in ProjectionExpression.To

Example code that throws the exception.

_userRepository.GetAll().OrderBy(x => x.Id).ProjectTo<User>.ToListAsync();

This works fine in a non-test scenario, but when I mock the DbSet using the TestDbAsyncEnumerable I get

: Unable to cast object of type 'Namespace.TestDbAsyncEnumerable`1[UserEntity]' to type 'System.Linq.IQueryable`1[User]'.

Right now to get around this I have to ProjectTo after the call to the Async EF extensions. Is there any way to keep the ProjectTo call before the EF extensions?

Reference code:

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

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

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

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

    IQueryProvider IQueryable.Provider => new TestDbAsyncQueryProvider<T>(this);
}

public static Mock<DbSet<T>> ToAsyncDbSetMock<T>(this IEnumerable<T> source)
        where T : class
    {

        var data = source.AsQueryable();

        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;
    }
like image 333
Dylan Musil Avatar asked Jun 28 '17 16:06

Dylan Musil


3 Answers

Edit your TestDbAsyncQueryProvider<>.CreateQuery() so that it returns the right type of the expression passed by ProjectTo<>.

Here is my sample implementation.

public IQueryable CreateQuery(Expression expression)
{
    switch (expression)
    {
        case MethodCallExpression m:
            {
                var resultType = m.Method.ReturnType; // it shoud be IQueryable<T>
                var tElement = resultType.GetGenericArguments()[0];
                var queryType = typeof(TestDbAsyncEnumerable<>).MakeGenericType(tElement);
                return (IQueryable)Activator.CreateInstance(queryType, expression);
            }
    }
    return new TestDbAsyncEnumerable<TEntity>(expression);
}

https://gist.github.com/masaedw/95ab972f8181de6bbe48a20ffe9be113

I have written also unit test. It's working.

https://github.com/masaedw/AutoMapper/blob/TestDbAsync/src/IntegrationTests/MockedContextTests.cs

like image 130
Masayuki Muto Avatar answered Nov 16 '22 07:11

Masayuki Muto


I ran into this same problem, in addition to the accepted answer you might also have the generic version of CreateQuery like I do - I fixed that too like this:

public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
    var queryType = typeof(TestDbAsyncEnumerable<>).MakeGenericType(typeof(TElement));
    return (IQueryable<TElement>)Activator.CreateInstance(queryType, expression);
}

The type is being provided by TElement, so its a simplier implementation on the generic version.

like image 6
Ian Robertson Avatar answered Nov 16 '22 08:11

Ian Robertson


I was getting this same error in my tests after upgrading from Automapper 6.0.2 to 6.1.1. Downgrading back to 6.0.2 fixed the issue.

Not sure if this is a regression or a breaking change in Automapper. I haven't had time to pursue it further than reviewing the change log and github issues. Nothing jumps out.

like image 4
jslatts Avatar answered Nov 16 '22 07:11

jslatts