为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
肥皂起泡泡 2023-08-13 13:57:51
当直接从数据库提供 IEnumerable 时,控制器如何工作?哪个代码更正确、更优化?假设数据库非常慢并且正在进行其他操作。这个示例非常简单,因此执行时间可能没有足够的差异,但我正在尝试学习最佳实践。#1public Task<Application[]> Find(Expression<Func<Application, bool>> predicate){    return DatabaseContext.Applications        .Where(predicate)        .ToArrayAsync();}...public Task<Application[]> Find(...){    return ApplicationService.Find(...);}#2public 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);}#3public IEnumerable<Application> Find(Expression<Func<Application, bool>> predicate){    return DatabaseContext.Applications;}...public IActionResult<IEnumerable<Application>> Find(...){    var applications = ApplicationService.Find(...);    return Ok(applications);}
查看完整描述

2 回答

?
呼啦一阵风

TA贡献1802条经验 获得超6个赞

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


(我假设你的意思是直接从你的IEnumerable返回一个未执行的)IQueryableDbContext


他们不会,你也不应该- 这是因为未执行的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()andthis.View()不会触发视图的评估或向客户端发送对象响应 - 相反,它会导致控制器操作首先结束,然后将数据传递到 ASP.NET 管道中的下一步(即视图) 。请记住:视图在控制器操作完成后执行。


如果您使用依赖注入在控制器中拥有一个现成的实例DbContext,那么结果就不太可预测:IQueryable在操作方法返回后仍然可以对 进行评估,因为DbContext直到控制器被处置之后才会被处置,通常是在视图已呈现,但是您仍然不应该这样做,因为您IQueryable仍然可能会传递到某个比您的 Controller 类的生命周期更长的进程,这会导致失败。您还应该避免它,因为视图被设计为快速同步渲染 - 外部数据库或 IO 调用会破坏该设计。


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


如果您总是使用操作async(DbContext例如ToListAsync(),ToDictionaryAsync等 - 因为它们分别返回 aTask<List<T>>或TaskDictionary<TKey,TValue>>- 这需要一个await编译器默认情况下会阻止您在视图或对象结果中执行的操作,则可以避免这种习惯(您可以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 );

    }

}


查看完整回答
反对 回复 2023-08-13
?
拉风的咖菲猫

TA贡献1995条经验 获得超2个赞

  1. 您正在返回一个由 mvc 框架执行的任务;

  2. 当你等待一个任务时,你就开始(异步)运行它,然后获取结果并将其交给 mvc 框架;

  3. 您将返回一个由 mvc 框架执行的枚举器。

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


查看完整回答
反对 回复 2023-08-13
  • 2 回答
  • 0 关注
  • 102 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信