为什么"Dispose"有效,但"using(var db = new DataContext())"无效?

9
我是一名有帮助的助手,以下是针对您提供的翻译:

我正在创建一个由主题组成的论坛,这些主题由消息组成。

当我尝试在控制器中实现主题视图时:

public ActionResult Topic(int id) //Topic Id
{
    using (var db = new DataContext())
    {
        var topic = db.Topics.Include("Messages").Include("Messages.CreatedBy").Include("CreatedBy").FirstOrDefault(x => x.Id == id);

        //include the messages for each topic, and when they were created so that the last message can be displayed on the topic page
        return topic != null ? View(topic) : View();
    }
}

当我尝试查看话题页面时,出现了以下错误:

用户代码未处理 ObjectDisposedException

ObjectContext 实例已被释放,不能再用于需要连接的操作。

该错误似乎与特定行无关,因为当我删除有问题的行时,同样的错误会更早地出现。

我已通过使用以下内容解决了此问题:

DataContext db = new DataContext();

在控制器开始时:
protected override void Dispose(bool disposing)
{
    db.Dispose();             
    base.Dispose(disposing);
}

在结尾处(并将“Using”去掉)
虽然这个方法可行,但我很好奇为什么“Using”不起作用,并且我不喜欢在整个控制器中保持连接,最后还要手动处理它。

你尝试过在外面使用return吗? - User.Anonymous
@ECH,你不能访问它,因为它被限定在using语句的范围之内。 - Dismissile
@Dismissile 是的,我认为 User.Anonymous 意味着在 using 语句之外返回其他东西。 - Ed Harrod
在某些情况下,您会返回View(topic)。看起来这个对象试图在上述方法返回后的某个时候使用DataContext。但是此时DataContext对象已被处理并且无法使用。这就是Dismissile在他的答案中所描述的。 - Jeppe Stig Nielsen
@vishalsharma 这个问题是指在返回之前关闭 using 语句,这样做(或不这样做)对这个问题都会导致相同的错误。 - Ed Harrod
显示剩余3条评论
4个回答

7
你们的实体中是否启用了延迟加载?看起来在你们的视图中执行了一些查询,但是在它们被执行之前就将上下文对象销毁了(因此出现了已经被销毁的错误)。如果你将上下文对象的销毁放在控制器的Dispose方法中,视图将在控制器和上下文对象被销毁之前得到执行。
我建议安装Glimpse.Mvc5和Glimpse.EF6包。一旦配置好glimpse,你可以查看页面中执行的每个查询。你可能会惊讶地发现还有一些未预期的额外查询被执行。这就是为什么我不建议直接在视图中使用实体。

不确定为什么会被踩,对我来说这似乎是一个非常可信的解释。 - Joachim Isaksson
请问您能告诉我 using 的语法吗?我只知道 using 用于导入命名空间,例如 using system; - HackerMan
1
@HackerMan - 如果某个东西实现了IDisposable接口,你可以在它周围放置一个using语句,这样它会在作用域结束时调用Dispose方法。例如:using(var db = new MyDbContext()) { } - 在作用域结束时,它将调用db的Dispose()方法。 - Dismissile
这是否意味着它调用了db dispose方法来清理资源? - HackerMan
1
@Chris - 不是的。如果你把using语句放在Controller.Dispose方法中,结果会在控制器被处理之前执行。如果你直接把using语句放在操作方法中,它会在作用域关闭之前被处理。 - Dismissile
显示剩余2条评论

3
这是因为通常LINQ实体都是代理对象。如果你有像MyEntity.ChildEntities这样的东西,底层的SQL查询不会在执行代码之前获取这些对象。如果你在视图中访问它们,视图直到操作方法返回后才绑定,此时DbContext已经被释放。
生命周期大致如下:
  1. 调用操作方法
  2. 运行获取topic的外部查询,但尚未执行触发更多SQL查询的视图中的任何其他访问器。
  3. 现在我们已经离开了操作方法,所以using刚刚释放了你的DbContext。
  4. MVC框架将模型绑定到视图,这会触发实际执行任何剩余查询并失败,因为DbContext已释放。
  5. 请求生命周期即将结束,因此控制器被释放。
这是一个关于使用实体进行延迟加载的好资源。

据我所知,FirstOrDefault会执行查询。 - GSerg
FirstOrDefault执行查询。它是一个代理还是非代理并不重要。其他查询可以通过延迟加载执行,但FirstOrDefault肯定会执行查询。 - Dismissile
@GSerg 是的,FirstOrDefault 执行查询并返回单个实体。在范围结束后访问任何 其他 链接的(且未被急切获取的)实体都会引发此错误。 - Joachim Isaksson
是的,确实如此。这就是我因为匆忙浏览问题而得到的结果。 - Rex M
1
根本问题仍然是相同的——延迟执行。只是不是外部查询而已。对措辞进行了一些微调,以更清楚地描述这个确切的情况。 - Rex M

1
这是一个与延迟加载相关的问题,非常感谢@Dismissile指出了正确方向。
一旦我使用“.Include”加载Topic的每个虚拟属性,它就正常工作了:
var topic = db.Topics.Include("Messages").Include("Messages.CreatedBy").Include("CreatedBy").Include("Forum").Include("DeletedBy").FirstOrDefault(x => x.Id == id);

我猜如果没有我说,任何人都不可能知道我声明了哪些属性为虚拟的,抱歉!
顺便说一下,在调试模式下检查属性时之前是可以工作的,因为每个属性都必须被加载才能进行检查。

这是我建议的相同解决方案!!即,延迟加载问题和include()。 - stackunderflow
@ECH - 如果我们的解决方案对您有帮助,请标记其中一个作为答案。 - Dismissile
@stackunderflow 您的建议“使用Include()运算符”有点令人困惑,因为我已经在使用它了,但还是谢谢。 - Ed Harrod
不用担心,我会尽力写出详细的答案。只是需要时间,对于任何困惑感到抱歉。 - stackunderflow
1
亲爱的ECH,正如我承诺的那样,我设置了一个项目来回答你的问题。然后我遇到了一些问题,这引发了一个新的问题,所以我在这里发布了一个新的问题链接:https://dev59.com/Un_aa4cB1Zd3GeqP0j5M。我希望这个链接能够对你的原始问题有一个好的答案,对我造成的任何困惑表示抱歉 :) - stackunderflow

0

您不能使用上下文返回视图或内部内容,因为这会导致上下文无法正确处理。

在您的情况下,正确使用using语句的方式是

public ActionResult Topic(int id) //Topic Id
{
    Topic topic = null; // topic is your POCO
    using (var db = new DataContext())
    {
        topic = db.Topics.Include("Messages").Include("Messages.CreatedBy").Include("CreatedBy").FirstOrDefault(x => x.Id == id);   
    }
    return topic != null ? View(topic) : View();

}

如果您正在使用语句,请不要执行响应重定向操作。

请参见此处:在MVC中使用using语句处理数据库上下文的释放


谢谢您的帮助,我尝试了,但它并没有影响结果,我仍然得到相同的错误。 - Ed Harrod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接