如何模拟实体框架6异步投影查询

时间:2014-03-31 19:15:38

标签: entity-framework mocking entity-framework-6

通过利用Testing with async queries section of the Testing with a Mocking Framework article on MSDN,我已经能够创建许多成功通过测试。

这是我的测试代码,它使用NSubstitute进行模拟:

var dummyQueryable = locations.AsQueryable();

var mock = Substitute.For<DbSet<Location>, IDbAsyncEnumerable<Location>, IQueryable<Location>>();
((IDbAsyncEnumerable<Location>)mock).GetAsyncEnumerator().Returns(new TestDbAsyncEnumerator<Location>(dummyQueryable.GetEnumerator()));
((IQueryable<Location>)mock).Provider.Returns(new TestDbAsyncQueryProvider<Location>(dummyQueryable.Provider));
((IQueryable<Location>)mock).Expression.Returns(dummyQueryable.Expression);
((IQueryable<Location>)mock).ElementType.Returns(dummyQueryable.ElementType);
((IQueryable<Location>)mock).GetEnumerator().Returns(dummyQueryable.GetEnumerator());
sut.DataContext.Locations = mock;

var result = await sut.Index();

result.Should().BeView();

sut.Index()做得不多,但会进行以下查询:

await DataContext.Locations
    .GroupBy(l => l.Area)
    .ToListAsync());

这在我向查询添加投影之前一直正常工作:

await DataContext.Locations
    .GroupBy(l => l.Area)
    .Select(l => new LocationsIndexVM{ Area = l.Key }) // added projection
    .ToListAsync());

导致此异常:

System.InvalidOperationException
The source IQueryable doesn't implement IDbAsyncEnumerable<LocationsIndexVM>. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
   at System.Data.Entity.QueryableExtensions.AsDbAsyncEnumerable(IQueryable`1 source)
   at System.Data.Entity.QueryableExtensions.ToListAsync(IQueryable`1 source)
   at Example.Web.Controllers.HomeController.<Index>d__0.MoveNext() in HomeController.cs: line 25
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Example.Test.Web.Controllers.HomeControllerShould.<TempTest>d__4.MoveNext() in HomeControllerShould.cs: line 71

更新:我已经uploaded a small, simple solution重现了这个问题。

任何人都可以提供一个示例,说明单元测试async并包含.Select()投影的查询所需的内容吗?

1 个答案:

答案 0 :(得分:16)

所以我做了一些挖掘,问题与TestDbAsyncEnumerable<T>暴露IQueryProvider的方式有关。关于推理的最佳猜测如下,以及下面的解决方案。

TestDbAsyncEnumerable<T>继承自EnumerableQuery<T>,后者继承自IQueryable<T>,并显式实现此接口的Provider属性:

IQueryProvider IQueryable.Provider { get ... }

鉴于它是明确实现的,我假设LINQ内部在尝试获取Provider之前显式地转换了类型:

((IQueryable<T>)source).Provider.CreateQuery(...);

我手边没有源代码(并且不会费心寻找),但我相信类型绑定规则对于显式实现是不同的;实质上,Provider上的TestDbAsyncEnumerable<T>属性不被视为IQueryable<T>.Provider的实现,因为明确的属性存在于链的后面,因此永远不会返回TestDbAsyncQueryProvider<T>

解决此问题的方法是让TestDbAsyncEnumerable<T>也继承IQueryable<T>并明确实施Provider属性,如下所示(根据MSDN article you linked进行调整):

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
    {
        get { return new TestDbAsyncQueryProvider<T>(this); }
    }
}