C# 直接从数据库返回IEnumerable或之前使用ToListSync
当直接从数据库中为控制器提供IEnumerable时,控制器是如何工作的?哪种代码更正确、更优化?让我们假设数据库非常慢,并且还有其他操作正在进行 这个例子非常简单,所以在执行时间上可能没有足够的差异,但我正在尝试学习最佳实践 #1C# 直接从数据库返回IEnumerable或之前使用ToListSync,c#,asp.net-core,C#,Asp.net Core,当直接从数据库中为控制器提供IEnumerable时,控制器是如何工作的?哪种代码更正确、更优化?让我们假设数据库非常慢,并且还有其他操作正在进行 这个例子非常简单,所以在执行时间上可能没有足够的差异,但我正在尝试学习最佳实践 #1 公共任务查找(表达式谓词) { 返回DatabaseContext.Applications .Where(谓词) .ToArrayAsync(); } ... 公共任务查找(…) { 返回ApplicationService.Find(…); } #2 公共任务
公共任务查找(表达式谓词)
{
返回DatabaseContext.Applications
.Where(谓词)
.ToArrayAsync();
}
...
公共任务查找(…)
{
返回ApplicationService.Find(…);
}
#2
公共任务查找(表达式谓词)
{
返回DatabaseContext.Applications
.Where(谓词)
.ToListAsync();
}
...
公共异步任务查找(…)
{
var applications=wait ApplicationService.Find(…)
返回Ok(应用程序);
}
#3
public IEnumerable Find(表达式谓词)
{
返回DatabaseContext.Applications;
}
...
公共IActionResult查找(…)
{
var applications=ApplicationService.Find(…);
返回Ok(应用程序);
}
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()
不会触发对视图的求值或向客户端发送对象响应,而是会导致控制器操作首先结束,然后将数据传递到ASP.NET管道中的下一步(即视图)。请记住:视图是在控制器操作完成后执行的
如果您使用依赖项注入在控制器中拥有DbContext
的就绪实例,则结果不太可预测:在操作方法返回后仍然可以评估IQueryable
,因为DbContext
直到控制器被释放后才会被释放,这通常是在视图被呈现,但是您仍然不应该这样做,因为您的IQueryable
仍可能被传递到某个超出控制器类生命周期的进程,这将导致失败。您还应该避免这种情况,因为视图是为了快速同步地呈现而设计的——使用外部数据库或IO调用会破坏这种设计
(无论如何,您不应该使用实体框架实体对象作为根视图模型,但这是另一个讨论)
如果始终在DbContext
上使用async
操作(例如ToListAsync()),则可以避免此习惯
、ToDictionaryAsync
等-因为它们分别返回任务
或任务字典>
-这需要等待
,编译器默认情况下会阻止您在视图或对象结果中执行该操作(您可以在视图中设置wait
,但这是不可取的,需要在某处设置一些设置)
简而言之,始终要这样做:
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 );
}
}
public异步任务GetPeople()
{
使用(MyDbContext db=newmydbcontext(GetConnectionString()))
{
List=等待db.People
.Where(p=>p.Name==“约翰·史密斯”)
.ToListAsync();
//WebAPI:
返回此。确定(列表);//返回已计算的列表,并加载到内存中。(确保禁用了惰性导航属性)
//MVC:
PeopleListViewModel vm=new PeopleListViewModel();//在MVC中,始终为根视图模型使用自定义类,这样您就不会直接接受或返回实体框架实体类型
vm.List=List;
返回此.View(vm);
}
}
正是我所希望的。我只是想控制器会锁定线程,以执行查询阻塞整个API几毫秒(或不)当我可以异步执行时..无论如何,我自己这样做似乎是最好的选择。感谢您的解释。我的意思是,i/O操作不是异步的,在等待整个数据库响应时不会让其他操作完成。@MateuszSurma ASP.NET默认情况下为每个请求/响应使用专用线程(我认为ASP.NETCore也是如此)-因此,在整个线程池耗尽之前,一个请求不会阻止另一个请求。
public IEnumerable<Application> Find(Expression<Func<Application, bool>> predicate)
{
return DatabaseContext.Applications;
}
...
public IActionResult<IEnumerable<Application>> Find(...)
{
var applications = ApplicationService.Find(...);
return Ok(applications);
}
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" ) );
}
}
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 );
}
}