简体   繁体   English

直接从数据库返回 IEnumerable 或之前使用 ToListAsync

[英]Returning IEnumerable straight from database or using ToListAsync before

How do the controllers work when giving them IEnumerable straight from the database?当控制器直接从数据库提供 IEnumerable 时,它​​们是如何工作的? Which code is more correct and optimal?哪个代码更正确和最优? Let's assume that the database is very slow and there are other operations going on.让我们假设数据库非常慢,并且还有其他操作正在进行。

This example is very simple so there might not be enough difference in execution times but i am trying to learn the best practices.这个例子非常简单,因此执行时间可能没有足够的差异,但我正在努力学习最佳实践。

#1 #1

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

...

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

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

How do the controllers work when giving them IEnumerable straight from the database?当控制器直接从数据库提供 IEnumerable 时,它​​们是如何工作的?

(By IEnumerable I assume you mean directly returning an unexecuted IQueryable from your DbContext ) (通过IEnumerable我假设你的意思是直接从你的DbContext返回一个未执行的IQueryable

They don't and you shouldn't - this is because an unexecuted IQueryable does not represent loaded data - and when it is executed it can only load data from an open database connection - which requires an active and valid DbContext .他们不会,你也不应该——这是因为未执行的IQueryable不代表加载的数据——并且当它被执行时,它只能从打开的数据库连接加载数据——这需要一个活动且有效的DbContext

...so if the DbContext is disposed, then the IQueryable cannot be executed. ...因此,如果DbContextDbContext ,则无法执行IQueryable

If you create the DbContext inside the controller action and render the IQueryable in your view or return it in an ObjectResponse (for Web API) then it will always fail:如果您在控制器操作中创建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" ) );
    }
}

Remember that .Ok() and this.View() does not trigger evaluation of the View or send an object-response to the client - instead it causes the Controller action to end first and then pass the data on to the next step in the ASP.NET pipeline (ie the view).请记住, .Ok()this.View()不会触发对视图的评估或向客户端发送对象响应 - 相反,它会导致控制器操作首先结束,然后将数据传递到ASP.NET 管道(即视图)。 Remember: views are executed after the controller action is finished.记住:视图是在控制器动作完成后执行的。

If you use Dependency Injection to have a ready instance of your DbContext in the Controller then the results are less predictable: the IQueryable can still be evaluated after the action method returns because the DbContext won't be disposed until after the Controller is disposed which is generally after the view is rendered, however you still shouldn't do this because your IQueryable could still be passed-on to some process that outlives the life of your Controller class which would then cause a failure.如果您使用依赖注入在控制器中拥有一个准备好的DbContext实例,那么结果将不太可预测: IQueryable仍然可以在操作方法返回后进行评估,因为DbContext不会在控制器被DbContext之后被DbContext ,即通常呈现视图之后,但是您仍然不应该这样做,因为您的IQueryable仍然可以传递给某个进程,该进程比您的 Controller 类的生命周期更长,然后会导致失败。 You should also avoid it because Views are designed to be rendered quickly and synchronously - having an external database or IO call breaks that design.你也应该避免它,因为视图被设计为快速和同步呈现 - 使用外部数据库或 IO 调用会破坏该设计。

(And you shouldn't use Entity Framework entity objects as root ViewModels anyway, but that's another discussion). (无论如何,您都不应该使用 Entity Framework 实体对象作为根 ViewModel,但这是另一个讨论)。

You can avoid this habit if you always use the async operations on DbContext (eg ToListAsync() , ToDictionaryAsync , etc - because those return a Task<List<T>> or TaskDictionary<TKey,TValue>> respectively - which require an await which the compiler will prevent you from doing in a View or Object result by default (you can have await in views, but it's inadvisable and requires setting some settings somewhere).如果您总是在DbContext上使用async操作(例如ToListAsync()ToDictionaryAsyncToListAsync() ,则可以避免这种习惯 - 因为它们分别返回Task<List<T>>TaskDictionary<TKey,TValue>> - 这需要await默认情况下,编译器将阻止您在视图或对象结果中执行操作(您可以在视图中使用await ,但这是不可取的,并且需要在某处设置一些设置)。

In short, always do this:简而言之,始终这样做:

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 );
    }
}
  1. You are returning a task to be executed by the mvc framework;您正在返回一个要由 mvc 框架执行的任务;
  2. You are running (asynchronously) a task as soon as you await it, then getting the result and handing it to the mvc framework;一旦等待任务,您就在运行(异步)任务,然后获取结果并将其交给 mvc 框架;
  3. You are returning an enumerator to be executed by the mvc framework.您正在返回一个由 mvc 框架执行的枚举器。

I would go with option #2 because you know exactly when the database query will be executed.我会选择选项 #2,因为您确切知道何时执行数据库查询。 Since you are returning a Task and correctly using async and await keywords, the framework will keep as many threads as busy as it can, leveraging the throughput of the application.由于您正在返回一个 Task 并正确使用 async 和 await 关键字,因此该框架将利用应用程序的吞吐量保持尽可能多的线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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