22

我对 ASP.NET MVC 应用程序中实体框架上下文的期望生命周期有一些疑问。让上下文在最短的时间内保持活跃不是最好的吗?

考虑以下控制器操作:

public ActionResult Index()
{
    IEnumerable<MyTable> model;

    using (var context = new MyEntities())
    {
        model = context.MyTable;
    }

    return View(model);
}

上面的代码不起作用,因为在视图呈现页面时实体框架上下文超出了范围。其他人将如何构建上面的代码?

4

4 回答 4

52

让我们有争议!

我不同意 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 而中断吗?是的,它会迫使您编辑数据层以获取视图层所需的数据。这是应该的。

代码异味 从您的视图中访问数据库是有问题的。您知道 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 上下文。这样做是因为您真正关心它的好处并了解它是如何正确完成的。

于 2012-05-28T10:06:24.267 回答
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);
}

这将帮助您避免无意中从您的视图中增加查询。

于 2012-05-27T22:27:03.767 回答
2

首先,您应该考虑将您的数据库访问权限放到单独的类中。

其次,我最喜欢的解决方案是使用“每个请求一个上下文”(如果您使用 MVC,我相信它是每个控制器一个上下文)。

要求的编辑:

看看这个答案,也许它也会帮助你。请注意我正在使用网络表单,因此目前无法在 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 容器来进一步增加趣味性……但我更喜欢这种方法,因为它很简单。

于 2012-05-27T22:22:11.830 回答
1

你能.ToList()像这样利用LINQ的扩展方法吗:

public ActionResult Index()
{
    IEnumerable<MyTable> model;

    using (var context = new MyEntities())
    {
        model = (from mt in context.MyTable
                select mt).ToList();
    }
    return View(model);
}
于 2012-05-27T22:27:23.007 回答