我正在尝试用FakeDbSet制作一个FakeDbContext进行单元测试。
但是我收到以下错误(见下文)。我正在扩展DbSet,所以通常应该实现IDbAsyncEnumerable。当我实现它时,它说没有用。
例外:
System.InvalidOperationException:源IQueryable没有 实行 IDbAsyncEnumerable。只要 实现IDbAsyncEnumerable的源可用于实体 框架异步操作。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=287068
FakeDbSet类:
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; }
}
这是代码的要点。在gist的最后一个文件中,这是错误发生的地方。 Gist code
答案 0 :(得分:29)
您的方案在随异常消息(http://go.microsoft.com/fwlink/?LinkId=287068)提供的链接中明确提到。缺少的成分是您应该从您的Provider属性返回的IDbAsyncQueryProvider。
只需浏览链接即可到达the boilerplate implementation。
我可以补充一下,我只会引用基本短语:
为了使用异步查询,我们需要做更多的工作。如果我们尝试将我们的Moq DbSet与GetAllBlogsAsync方法一起使用,我们将得到以下异常:
System.InvalidOperationException:源IQueryable未实现IDbAsyncEnumerable。只有实现IDbAsyncEnumerable的源才能用于Entity Framework异步操作。有关详细信息,请参阅http://go.microsoft.com/fwlink/?LinkId=287068。
为了使用异步方法,我们需要创建一个内存中的DbAsyncQueryProvider来处理异步查询。虽然可以使用Moq设置查询提供程序,但在代码中创建测试双重实现要容易得多。此实现的代码如下:
等...
答案 1 :(得分:15)
在我的情况下,异常是由使用错误的ToListAsync
扩展名引起的。
它来自:
using System.Data.Entity;
而不是
using Microsoft.EntityFrameworkCore;
更改命名空间修复了错误。
答案 2 :(得分:7)
我将here中的示例测试类重命名为删除单词Test
,因为它们在测试之外非常有用:
DbAsyncEnumerable
DbAsyncEnumerator<T>
DbAsyncQueryProvider<TEntity>
然后我在下面添加了扩展类,现在你可以做...
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
}.AsAsyncQueryable(); // <<== new extension method
这不仅在单元测试中有用,而且在您希望实现返回异步数据库查询的IQueryable<T>
接口或者随后可以安全地调用query.ToAsyncArray()
的内存数据时也是如此。< / p>
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);
}
}
答案 3 :(得分:5)
对于使用Microsoft上面讨论的样板代码的人来说,这是一个快速帮助类,它可以将您的模拟数据转换为异步结果。只需添加到MS代码的底部并使用类似
的方式调用 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;
}
}
它与MS示例中的代码相同,但是通用且可从许多不同的单元测试中重复使用。
答案 4 :(得分:3)
要解决我的 IDbAsyncEnumerable 问题:
将我的项目目标从 .NetFramework 4.0 更改为 .NetFramework 4.5
重新安装 EntityFramework 6.1.3 Nuget包。
此时,我的Visual Studio的IDE 显示Potencial Fixes 顾问,允许我引用System.Data.Entity.Infrastructure命名空间
使用 System.Data.Entity.Infrastructure ;
答案 5 :(得分:1)
DbSet
可能隐式实现IDbSet
,因此这些方法不适用于派生类中的interface mapping。
不要来自IDbSet<TEntity>
。
答案 6 :(得分:0)
没有直接关系,但可能会帮助其他人。如果您使用的是DelegateCompiler,请确保在EF软件包(source)中使用的是DecompileAsync
,而不是Decompile
。