![](/img/trans.png)
[英]Running parallel async tasks and return result in .NET Core Web API
[英].Net Core 2.2 web api with Entity Framework and Linq not able to do async tasks?
我有一个.NET Core 2.2 Web API,我想让控制器异步返回结果。 一直处于异步状态,在浏览器中调用以通过id测试get并使其全部正常工作。
控制器单元测试也可以正常工作,但是当我创建涉及模拟上下文的服务级别单元测试时,遇到了错误
System.AggregateException : One or more errors occurred. (The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IAsyncQueryProvider can be used for Entity Framework asynchronous operations.)
当我研究此错误时,我遇到了博客和stackoverflow文章,这些文章说,唯一的方法是将代码包装在Task.FromResult中。
其中一篇文章是: https : //expertcodeblog.wordpress.com/2018/02/19/net-core-2-0-resolve-error-the-source-iqueryable-doesnt-implement-iasyncenumerable/
这意味着EF实际上无法执行实际的异步工作,或者我不了解基本的内容(第二种选择可能是最可能的-但我想确认)。
在代码方面,我的服务如下(只是使用get方法来缩小此范围)
namespace MoneyManagerAPI.Services
{
public class CheckingService : ICheckingService
{
readonly CheckbookContext context;
public CheckingService(CheckbookContext context)
{
this.context = context;
}
public async Task<Checking[]> GetAllRecordsAsync()
{
return await Task.FromResult(context.Checking.OrderByDescending(m => m.Id).ToArray());
}
public async Task<Checking> GetByIdAsync(int id)
{
return await Task.FromResult(context.Checking.FirstOrDefault(c => c.Id == id));
//return await context.Checking.FirstOrDefaultAsync(c => c.Id == id);
}
}
}
在GetByIdAsync方法中,如果注释掉的代码行未注释,而另一个return语句被注释,则代码仍会编译,但在测试时会抛出异常方法。
我的测试类具有以下代码:
namespace Unit.Services
{
[TestFixture]
public class CheckingServiceTests : CheckingHelper
{
[Test]
public void GetAllRecordsAsync_ShouldReturnAllRecords()
{
// arrange
var context = this.CreateCheckingDbContext();
var service = new CheckingService(context.Object);
var expectedResults = Task.FromResult(CheckingHelper.GetFakeCheckingData().ToArray());
// act
var task = service.GetAllRecordsAsync();
task.Wait();
var result = task.Result;
// assert
expectedResults.Result.Should().BeEquivalentTo(result);
}
[Test]
public void GetByIdAsync_ShouldReturnRequestedRecord()
{
// arrange
var id = 2;
var context = this.CreateCheckingDbContext();
var service = new CheckingService(context.Object);
var expectedResult = CheckingHelper.GetFakeCheckingData().ToArray()[1];
// act
var task = service.GetByIdAsync(id);
task.Wait();
var result = task.Result;
// assert
expectedResult.Should().BeEquivalentTo(result);
}
Mock<CheckbookContext> CreateCheckingDbContext()
{
var checkingData = GetFakeCheckingData().AsQueryable();
var dbSet = new Mock<DbSet<Checking>>();
dbSet.As<IQueryable<Checking>>().Setup(c => c.Provider).Returns(checkingData.Provider);
dbSet.As<IQueryable<Checking>>().Setup(c => c.Expression).Returns(checkingData.Expression);
dbSet.As<IQueryable<Checking>>().Setup(c => c.ElementType).Returns(checkingData.ElementType);
dbSet.As<IQueryable<Checking>>().Setup(c => c.GetEnumerator()).Returns(checkingData.GetEnumerator());
var context = new Mock<CheckbookContext>();
context.Setup(c => c.Checking).Returns(dbSet.Object);
return context;
}
}
}
最后,GetFakeCheckingData如下:
namespace Unit.Shared
{
public class CheckingHelper
{
public static IEnumerable<Checking> GetFakeCheckingData()
{
return new Checking[3]
{
new Checking
{
AccountBalance = 100,
Comment = "Deposit",
Confirmation = "Test Rec 1",
Credit = true,
Id = 1,
TransactionAmount = 100,
TransactionDate = new DateTime(2019, 8, 1, 10, 10, 10)
},
new Checking
{
AccountBalance = 90,
Comment = "Withdrawal",
Confirmation = "Test Rec 2",
Credit = false,
Id = 2,
TransactionAmount = -10,
TransactionDate = new DateTime(2019, 8, 10, 10, 10, 10)
},
new Checking
{
AccountBalance = 50,
Comment = "Deposit",
Confirmation = "Test Rec 3",
Credit = true,
Id = 3,
TransactionAmount = 50,
TransactionDate = new System.DateTime(2019, 9, 21, 10, 10, 10)
}
};
}
}
}
不要在原处使用Task.FromResult
,因为这将导致实时代码同步运行(当您提要await
一个完成的Task
, Task.FromResult
执行此操作,所有内容都将同步运行)。 那会伤害您的生产代码。
微软在这里有关于如何在EF6中处理此问题的文档,尽管看起来它也可以将其应用于EF Core。 解决方案是为测试创建自己的异步方法。
但是,你可以看看重构你的测试代码使用的内存数据库,为EF核心文档解释了这里 。 这样做的好处是您可以使用相同的CheckbookContext
而不使用Mock
,因此异步方法应该仍然可以正常工作。
供以后参考,当您看到AggregateException
,表示正在抛出合法异常,它只是包装在AggregateException
。 如果检查AggregateException
的InnerExceptions
属性,您将看到实际的异常。
为了避免将真正的异常放入AggregateException
,请不要使用.Wait()
。 使您的测试方法async Task
并使用await
。
如果由于某种原因您不能使它们异步,则请使用.GetAwaiter().GetResult()
,它仍然会阻塞线程,但会给您真正的异常。
完成此操作后,您仍然会遇到异常,但这将向您显示实际问题。
对于应用程序流和单元测试流,您都应遵循异步等待模式。 您应该将代码保留为
public async Task<Checking> GetByIdAsync(int id)
{
return await context.Checking.FirstOrDefaultAsync(c => c.Id == id);
}
并将单元测试方法签名更改为async Task
。 下面的书面异步单元测试以async
方式调用async
方法。
[Test]
public async Task GetByIdAsync_ShouldReturnRequestedRecord()
{
// arrange
var id = 2;
var context = this.CreateCheckingDbContext();
var service = new CheckingService(context.Object);
var expectedResult = CheckingHelper.GetFakeCheckingData().ToArray()[1];
// act
var result = await service.GetByIdAsync(id);
// assert
expectedResult.Should().BeEquivalentTo(result);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.