关于实体框架上下文生命周期的问题

时间:2012-05-27 22:07:15

标签: c# asp.net-mvc entity-framework using idisposable

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

考虑以下控制器操作:

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

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

    return View(model);
}

上面的代码无效,因为在视图呈现页面时,实体框架上下文已超出范围。其他人如何构建上面的代码?

4 个答案:

答案 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);
}