我对ASP.NET MVC应用程序中实体框架上下文的期望生命周期有一些疑问。是不是最好尽可能在最短的时间内保持上下文?
考虑以下控制器操作:
public ActionResult Index()
{
IEnumerable<MyTable> model;
using (var context = new MyEntities())
{
model = context.MyTable;
}
return View(model);
}
上面的代码无效,因为在视图呈现页面时,实体框架上下文已超出范围。其他人如何构建上面的代码?
答案 0 :(得分:46)
让我们引起争议!
我不同意MVC + EF的一致意见,即在整个请求中保持上下文活动是一件好事,原因有很多:
低效率提升 你知道创建一个新的数据库上下文有多昂贵吗?好吧......“一个DataContext是轻量级的,创建并不昂贵”来自MSDN
让IoC出错,看起来不错......直到你上线 如果你设置你的IoC容器来处理你的上下文并且你弄错了,那你真的错了。我已经两次现在看到从IoC容器创建的大量内存泄漏并不总是正确地处理上下文。在正常的并发用户级别中,服务器开始崩溃之前,您不会意识到自己设置错误了。它不会在开发中发生,所以进行一些负载测试!
意外延迟加载 您返回最新文章的IQueryable,以便您可以在主页上列出它们。有一天,要求其他人显示相应文章旁边的评论数量。因此,他们在视图中添加了一些简单的代码,以显示评论计数......
@foreach(var article in Model.Articles) {
<div>
<b>@article.Title</b> <span>@article.Comments.Count() comments</span>
</div>
}
看起来很好,工作正常。但实际上您没有在返回的数据中包含注释,因此现在这将为循环中的每篇文章进行新的数据库调用。选择N + 1问题。 10篇文章= 11个数据库调用。好的,所以代码是错误的,但这样做很容易就会发生错误。
您可以通过在数据层中关闭上下文来防止这种情况发生。但是,在article.Comments.Count()上,代码是否会因NullReferenceException而中断?是的,它会强制您编辑数据图层以获取View图层所需的数据。这应该是怎么回事。
代码味道 从View中访问数据库有一些问题。你知道IQueryable实际上并没有真正命中数据库,所以忘了那个对象。确保数据库在离开数据层之前已被命中。
答案
您的代码应该(在我看来)像这样
数据层:
public List<Article> GetArticles()
{
List<Article> model;
using (var context = new MyEntities())
{
//for an example I've assumed your "MyTable" is a table of news articles
model = (from mt in context.Articles
select mt).ToList();
//data in a List<T> so the database has been hit now and data is final
}
return model;
}
控制器:
public ActionResult Index()
{
var model = new HomeViewModel(); //class with the bits needed for you view
model.Articles = _dataservice.GetArticles(); //irrelevant how _dataService was intialised
return View(model);
}
一旦你完成了这一点并理解了这一点,那么也许你可以开始尝试拥有一个IoC容器句柄上下文,但绝对不是之前。我的警告 - 我已经看到两次大规模的失败:)
但老实说,做你喜欢的,编程很有趣,应该是一个偏好的问题。我只是告诉你我的。但无论你做什么,都不要开始在每个控制器或每个请求中使用IoC上下文,因为“所有很酷的孩子都在这样做”。这样做是因为你真正关心它的好处并理解它是如何正确完成的。
答案 1 :(得分:6)
我同意每个请求一个上下文,我们通常通过绑定上下文来实现这一点。使用Ninject的InRequestScope,非常有效,是:
Bind<MyContext>().ToSelf().InRequestScope();
这也是一个很好的做法,可以将集合枚举为尽可能接近查询,即:
public ActionResult Index()
{
IEnumerable<MyTable> model;
using (var context = new MyEntities())
{
model = (from mt in context.MyTable
select mt).ToArray();
}
return View(model);
}
这有助于您避免无意中从视图中扩充查询。
答案 2 :(得分:2)
首先,您应该考虑将数据库访问权限放在单独的类中。
其次,我最喜欢的解决方案是使用“每个请求一个上下文”(如果你使用的是MVC,我相信它是每个控制器的一个上下文)。
请求修改
看看这个答案,也许它对你有帮助。请注意我正在使用webforms,因此目前无法在MVC中验证它,但它可能对您有所帮助或至少给您一些指示。 https://stackoverflow.com/a/10153406/1289283
此dbcontext的一些示例用法:
public class SomeDataAccessClass
{
public static IQueryable<Product> GetAllProducts()
{
var products = from o in ContextPerRequest.Current.Products
select o;
return products;
}
}
然后你可以这样做:
public ActionResult Index()
{
var products = SomeDataAccessClass.GetProducts();
return View(products);
}
简单,对吧?您不必再担心处理上下文,只编写您真正需要的代码。
有些人喜欢通过添加UnitOfWork模式或者IoC容器来进一步增加一些东西...但我更喜欢这种方法,因为它简单。
答案 3 :(得分:1)
您可以使用LINQ的.ToList()
扩展方法:
public ActionResult Index()
{
IEnumerable<MyTable> model;
using (var context = new MyEntities())
{
model = (from mt in context.MyTable
select mt).ToList();
}
return View(model);
}