【问题标题】:Returning IEnumerable straight from database or using ToListAsync before直接从数据库返回 IEnumerable 或之前使用 ToListAsync
【发布时间】:2019-08-06 13:40:49
【问题描述】:

当直接从数据库给控制器提供 IEnumerable 时,控制器是如何工作的?哪个代码更正确和最优?让我们假设数据库非常慢并且还有其他操作正在进行。

此示例非常简单,因此执行时间可能没有足够的差异,但我正在努力学习最佳实践。

#1

public Task<Application[]> Find(Expression<Func<Application, bool>> predicate)
{
    return DatabaseContext.Applications
        .Where(predicate)
        .ToArrayAsync();
}

...

public Task<Application[]> Find(...)
{
    return ApplicationService.Find(...);
}

#2

public Task<List<Application>> Find(Expression<Func<Application, bool>> predicate)
{
    return DatabaseContext.Applications
        .Where(predicate)
        .ToListAsync();
}

...

public async Task<IActionResult> Find(...)
{
    var applications = await ApplicationService.Find(...)
    return Ok(applications);
}

#3

public IEnumerable<Application> Find(Expression<Func<Application, bool>> predicate)
{
    return DatabaseContext.Applications;
}

...

public IActionResult<IEnumerable<Application>> Find(...)
{
    var applications = ApplicationService.Find(...);
    return Ok(applications);
}

【问题讨论】:

    标签: c# asp.net-core


    【解决方案1】:

    当直接从数据库给控制器提供 IEnumerable 时,控制器是如何工作的?

    IEnumerable 我假设您的意思是直接从您的DbContext 返回未执行的IQueryable

    他们不这样做,你也不应该 - 这是因为未执行的 IQueryable 不代表加载的数据 - 执行时它只能从打开的数据库连接加载数据 -这需要一个活跃且有效的DbContext

    ...所以如果DbContext被释放,那么IQueryable就不能被执行。

    如果您在控制器操作中创建DbContext 并在您的视图中呈现IQueryable 或在ObjectResponse(对于Web API)中返回它,那么它将始终失败:

    public IActionResult GetPeople()
    {
        // WARNING: NEVER DO THIS!
        using( MyDbContext db = new MyDbContext( GetConnectionString() ) )
        {
            return this.Ok( db.People.Where( p => p.Name == "John Smith" ) );
    
            // or:
    
            return this.View( model: db.People.Where( p => p.Name == "John Smith" ) );
        }
    }
    

    请记住,.Ok()this.View() 不会触发 View 的评估或向客户端发送对象响应 - 相反,它会导致 Controller 操作首先结束,然后将数据传递到下一步ASP.NET 管道(即视图)。请记住:视图是在控制器操作完成后执行的。

    如果您使用依赖注入在 Controller 中准备好您的 DbContext 实例,那么结果的可预测性会降低:在操作方法返回后仍然可以评估 IQueryable,因为 DbContext 不会直到控制器被处理之后,通常是在视图被渲染之后,但是你仍然不应该这样做,因为你的IQueryable 仍然可能被传递给一些超过生命周期的进程您的 Controller 类,这将导致失败。您还应该避免它,因为视图被设计为快速同步地呈现 - 具有外部数据库或 IO 调用会破坏该设计。

    (无论如何,您都不应该将 Entity Framework 实体对象用作根 ViewModel,但这是另一个讨论)。

    如果您始终对 DbContext 使用 async 操作(例如 ToListAsync()ToDictionaryAsync 等 - 因为它们分别返回 Task&lt;List&lt;T&gt;&gt;TaskDictionary&lt;TKey,TValue&gt;&gt;,则可以避免这种习惯 - 这需要 @ 987654344@,默认情况下编译器会阻止您在视图或对象结果中执行此操作(您可以在视图中有 await,但这是不可取的,需要在某处设置一些设置)。

    简而言之,总是这样做:

    public async Task<IActionResult> GetPeople()
    {
        using( MyDbContext db = new MyDbContext( GetConnectionString() ) )
        {
            List<Person> list = await db.People
                .Where( p => p.Name == "John Smith" )
                .ToListAsync();
    
            // WebAPI:
            return this.Ok( list ); // returning an evaluated list, loaded into memory. (Make sure Lazy Navigation Properties are disabled too)
    
            // MVC:
            PeopleListViewModel vm = new PeopleListViewModel(); // in MVC always use a custom class for root view-models so you're not accepting nor returning Entity Framework entity types directly
            vm.List = list;
    
            return this.View( vm );
        }
    }
    

    【讨论】:

    • 完全如我所愿。我只是在想,当我可以异步执行此操作时,控制器会锁定线程以执行阻塞整个 API 几毫秒(或不执行)的查询。 .无论如何,自己做这件事似乎是最好的选择。感谢您的解释。
    • 我的意思是 I/O 操作不是异步的,在等待整个数据库响应时不会让其他操作完成。
    • @MateuszSurma ASP.NET 默认为每个请求/响应使用一个专用线程(我认为 ASP.NET Core 也是如此) - 所以一个请求不会阻塞另一个请求,直到整个线程池完成筋疲力尽。
    【解决方案2】:
    1. 您正在返回要由 mvc 框架执行的任务;
    2. 您正在(异步)运行一个任务,等待它,然后获取结果并将其交给 mvc 框架;
    3. 您正在返回要由 mvc 框架执行的枚举器。

    我会选择选项 #2,因为您确切知道何时执行数据库查询。由于您正在返回一个 Task 并正确使用 async 和 await 关键字,因此框架将尽可能多地保持繁忙,从而利用应用程序的吞吐量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-18
      • 1970-01-01
      • 2012-10-24
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      • 2019-01-20
      相关资源
      最近更新 更多