AutoMapper无法将TestDbAsyncEnumerable强制转换为IQueryable

时间:2017-06-28 16:07:35

标签: c# entity-framework automapper

我已经从https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx实现了TestDbAsync假货,我希望能够在调用Async EF方法(ToListAsync,CountAsync等)之前使用AutoMapper投影到其他类型。

我在ProjectionExpression.To中获得了一个强制转换异常

抛出异常的示例代码。

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

这在非测试场景中运行良好,但是当我使用TestDbAsyncEnumerable模拟DbSet时我得到了

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

现在,为了解决这个问题,我在调用Async EF扩展后必须使用ProjectTo。有没有办法在EF扩展之前保持ProjectTo调用?

参考代码:

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;
    }

5 个答案:

答案 0 :(得分:17)

修改您的TestDbAsyncQueryProvider<>.CreateQuery(),使其返回ProjectTo<>传递的正确表达式。

这是我的示例实现。

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

我还写了单元测试。它正在工作。

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

答案 1 :(得分:4)

从Automapper 6.0.2升级到6.1.1后,我的测试中出现了同样的错误。降级回6.0.2修复了这个问题。

不确定这是Automapper中的回归还是重大变化。我没有时间去研究更改日志和github问题。什么都没有跳出来。

答案 2 :(得分:4)

我遇到了同样的问题,除了接受的答案之外你还可能像我一样拥有CreateQuery的通用版本 - 我修复了这个问题:

foreach(var prop in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.GetCustomAttribute<MyAttribute>() != null))
 {
     Expression<Func<string>> expression = () => prop.Name;
     this.WhenAnyDynamic(expression.Body, x => x.Value).Subscribe(x => Text3 = $"Text is {x}");
 }

该类型由TElement提供,因此它在通用版本上的实现更为简单。

答案 3 :(得分:2)

结合使用上述两个答案,可以帮助我在整个单元测试中使用.ProjectTo。

在我的TestDbAsyncQueryProvider实现中,我替换了以下两种方法:

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);
}

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

到目前为止,它可以与最新的EF6配合使用。

答案 4 :(得分:0)

万一实体上的某人有其他对象,则ProjectTo将崩溃或引发空引用对象的异常。

为此,解决方案是确保您的实体的所有对象都使用default()或类似的东西初始化。

在此处的最后一条评论/答案中找到答案-> Why is Automapper.ProjectTo() throwing a null reference exception?

P.S感谢IAsyncQueryProvider的实现:)