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;
}
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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With