繁体   English   中英

带有实体框架和Linq的.Net Core 2.2 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一个完成的TaskTask.FromResult执行此操作,所有内容都将同步运行)。 那会伤害您的生产代码。

微软在这里有关于如何在EF6中处理此问题的文档,尽管看起来它也可以将其应用于EF Core。 解决方案是为测试创建自己的异步方法。

但是,你可以看看重构你的测试代码使用的内存数据库,为EF核心文档解释了这里 这样做的好处是您可以使用相同的CheckbookContext而不使用Mock ,因此异步方法应该仍然可以正常工作。


供以后参考,当您看到AggregateException ,表示正在抛出合法异常,它只是包装在AggregateException 如果检查AggregateExceptionInnerExceptions属性,您将看到实际的异常。

为了避免将真正的异常放入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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM