【问题标题】:Why is it possible to enumerate a DbLinq query after calling Dispose() on the DataContext?为什么在 DataContext 上调用 Dispose() 后可以枚举 DbLinq 查询?
【发布时间】:2010-05-17 18:38:27
【问题描述】:

更新 - 答案显然是 DbLinq 没有正确实现 Dispose()。哦!


以下内容具有误导性 - 底线:DbLinq 不(尚未)等同于 LinqToSql,正如我最初提出这个问题时所假设的那样。谨慎使用!

我将存储库模式与 DbLinq 一起使用。我的存储库对象实现了IDisposable,而Dispose() 方法只做一件事——在DataContext 上调用Dispose()。每当我使用存储库时,我都会将其包装在 using 块中,如下所示:

public IEnumerable<Person> SelectPersons()
{
    using (var repository = _repositorySource.GetPersonRepository())
    {
        return repository.GetAll(); // returns DataContext.Person as an IQueryable<Person>
    }
}

这个方法返回一个IEnumerable&lt;Person&gt;,所以如果我的理解是正确的,那么在遍历Enumerable&lt;Person&gt;之前实际上不会对数据库进行查询(例如,通过将其转换为列表或数组或在@中使用它987654331@ 循环),如本例所示:

var persons = gateway.SelectPersons();
// Dispose() is fired here
var personViewModels = (
    from b in persons
    select new PersonViewModel
    {
        Id = b.Id,
        Name = b.Name,
        Age = b.Age,
        OrdersCount = b.Order.Count()
    }).ToList(); // executes queries

在本例中,Dispose() 在设置 persons 后立即被调用,这是一个 IEnumerable&lt;Person&gt;,这是唯一一次被调用。

那么,三个问题:

  1. 这是如何工作的?在 DataContext 已被处置后,已处置的 DataContext 如何仍会查询数据库以获取结果?
  2. Dispose() 究竟做了什么?
  3. 我听说没有必要(例如,参见 this question)处置 DataContext,但我的印象是这不是一个坏主意。是否有任何理由处置 DbLinq DataContext

【问题讨论】:

  • repository.GetAll() 方法有什么作用?它返回什么?
  • @Eclipsed4utoo,好问题。我注释了代码。

标签: c# dblinq


【解决方案1】:

1 这是如何工作的?已处置的 DataContext 如何在 DataContext 已处置后仍向数据库查询结果?

不起作用。有些东西你没有给我们看。我猜你的存储库类没有正确/在正确的时间处理DataContext,或者你在每个查询结束时敷衍地写ToList(),这完全否定了查询转换和延迟执行你正常得到。

在测试应用中尝试以下代码,我向您保证它会抛出ObjectDisposedException

// Bad code; do not use, will throw exception.
IEnumerable<Person> people;
using (var context = new TestDataContext())
{
    people = context.Person;
}
foreach (Person p in people)
{
    Console.WriteLine(p.ID);
}

这是最简单的可重现情况,它总是会抛出。另一方面,如果您改为写people = context.Person.ToList(),则查询结果已经在using枚举,我敢打赌这就是您的情况。

2 Dispose() 实际上做了什么?

除其他外,它设置了一个标志,表明 DataContext 已被释放,在每个后续查询中都会检查该标志,并导致 DataContext 抛出带有消息 Object name: 'DataContext accessed after Dispose.'.ObjectDisposedException

如果DataContext打开它并保持打开状态,它也会关闭连接。

3 我听说没有必要(例如,请参阅此问题)处置 DataContext,但我的印象是这不是一个坏主意。有什么理由不处理 LinqToSql DataContext?

DisposeDataContext 必要的,因为每隔一个IDisposable 就必须是Dispose。如果您未能处理 DataContext,您可能会泄漏连接。如果从 DataContext 检索到的任何实体保持活动状态,您也可能会泄漏内存,因为上下文为其实现的工作单元模式维护了一个内部身份缓存。但即使不是这种情况,这不是你关心的Dispose 方法在内部做什么。假设它做了一些重要的事情。

IDisposable 是一份合同,上面写着“清理可能不是自动的;你需要在完成后处理掉我。”如果您忘记Dispose,您无法保证对象是否有自己的终结器在您之后清理。实现可能会发生变化,这就是为什么依赖观察到的行为而不是显式规范不是一个好主意的原因。

如果使用空的Dispose 方法处理IDisposable,可能发生的最糟糕的事情是浪费了几个CPU 周期。如果您未能非平凡的实现处置IDisposable,可能发生的最糟糕的事情就是您泄漏了资源。这里的选择是显而易见的;如果你看到IDisposable,别忘了丢弃它。

【讨论】:

  • 我很高兴我问了这个问题。你还对我的问题过于简单化了。从技术上讲,我没有使用 LinqToSql,我使用的是 DbLinq(应该是“就像 LinqToSql”)和一个 SQLite 数据库,当我运行你的代码时,它不会抛出任何错误。所以,我现在认为 Dispose() 只是没有在 DbLinq 上实现(或没有正确实现)。无论如何,听起来我需要执行特定 DataContext within using 块所需的所有操作。
  • @DanM:确实很有趣。我猜它是LIKELinq to SQL 的字面 SQL 意义。 :-) 尽管如此,第 3 点可能是最重要且仍然有效的;调用Dispose 永远不会伤害你(嗯,WCF 除外),但 经常调用它。鉴于 DbLinq 代码库仍处于 0.2 版(即不是“稳定”),如果未来的实现更改破坏了此处不正确但有效的用例,我不会感到惊讶。
  • 哈哈,第二点最重要的一点是我学到了一些关于正确使用 DataContext 对象的方法。感谢您的帮助。
【解决方案2】:

“persons”是一个 IEnumerable 集合,DataContext(存储库)只需要进行 .GetNew 调用。

from/select/etc 关键字是添加到 System.Linq 命名空间中的扩展方法的语法糖。这些扩展方法添加了您在查询中使用的 IEnumerable 功能,而不是 DataContext。事实上,您完全可以在不使用 LINQ2SQL 的情况下完成所有这些工作,只需以编程方式创建一个 IEnumerable 来进行演示。

如果您尝试使用这些对象进行任何进一步的存储库 (DataContext) 调用,则会收到错误消息。

IEnumerable 集合将包含您存储库中的所有记录,这就是您不需要 DataContext 进行查询的原因。

扩展方法:http://msdn.microsoft.com/en-us/library/bb383977.aspx

LINQ 扩展方法: http://msdn.microsoft.com/en-us/library/system.linq.enumerable_members.aspx

【讨论】:

  • 我了解扩展方法,而且我一直在使用 LINQ(不仅仅是 LinqToSql),但我想我不明白 DataContext 究竟做了什么。我将Connection 对象传递给它的事实使我认为它必须负责查询数据库。然而,即使我处理掉它,查询显然仍然会被执行。这怎么可能?
  • 数据上下文是由 Visual Studio 工厂创建的,用于从您的数据库构建 IEnumerables。在查询中实际使用 DataContext 会比在单独的方法中使用 GetAll 更容易。
  • 您所做的查询不是针对数据库的查询,而是针对您从数据库中获取并之前存储在内存中的对象集合的查询(在您的示例中)。
  • 你错了,linq 查询只是创建了一个表达式树,linqtosql 实现了一个 iqueryable 提供程序,将其转换为在数据库中执行的 sql 查询。在枚举集合之前,不会执行此查询并将数据从数据库检索到内存中。
【解决方案3】:

在 API 的深处,你可能会看到一个使用这样一个 api 的方法:

http://msdn.microsoft.com/en-us/library/y6wy5a0f(v=VS.100).aspx

命令执行时,关闭关联的DataReader对象时,关闭关联的Connection对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-18
    • 2020-01-30
    • 2015-09-10
    • 1970-01-01
    相关资源
    最近更新 更多