为什么在调用DataContext的Dispose()方法后,仍然可以枚举DbLinq查询结果?

8
更新 - 显然,DbLinq没有正确实现Dispose()方法。唉!
以下内容有些误导性 - 底线是:DbLinq目前(还)无法与LinqToSql等效,这是我最初提出此问题时假定的。请谨慎使用!
我正在使用Repository模式和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<Person>,因此如果我理解正确,直到遍历 Enumerable<Person>(例如将其转换为列表或数组,或在 foreach 循环中使用它)之前,实际上并没有对数据库进行查询,就像这个例子一样:

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<Person>)之后立即被调用,并且那是唯一调用它的时间。
所以,有三个问题:
  1. 这是如何工作的?在DataContext被处理后,如何查询数据库获取结果?
  2. Dispose() 实际上是做什么的?
  3. 我听说不需要(例如,参见此问题)处理DataContext,但我的印象是这并不是一个坏主意。 有没有任何理由不要处理DbLinq的DataContext

repository.GetAll() 方法是做什么的?它返回什么? - Ryan Alford
@Eclipsed4utoo,好问题。我已经对代码进行了注释。 - devuxer
3个回答

3
这是怎么回事?在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?
需要处理DataContext,因为需要处理每个其他IDisposable。如果未处理DataContext,则可能会泄漏连接。如果从DataContext检索的任何实体保持活动状态,则可能会泄漏内存,因为上下文维护用于实现工作单元模式的内部身份缓存。但即使没有这些情况,也不必担心Dispose方法在内部执行的操作。假设它执行了一些重要操作。 IDisposable是一个合同,它说:“清理可能不是自动的;完成后必须处理我。”如果您忘记处理Dispose,则无法保证对象是否具有自己的终结器来在您之后进行清理。实现可能会发生更改,这就是为什么依赖于观察到的行为而不是显式规范不是一个好主意。
如果使用空的Dispose方法处理IDisposable,最糟糕的情况就是浪费一些CPU周期。如果未处理具有非平凡实现IDisposable,则最坏的情况是泄漏资源。这里的选择很明显;如果看到IDisposable,请勿忘记处理它。

1
我很高兴我问了这个问题。你也指出了我的问题有点过于简单化。实际上,我并没有使用LinqToSql,而是使用DbLinq(它应该与LinqToSql类似)和SQLite数据库。当我运行你的代码时,它不会抛出任何错误。因此,我现在认为,在DbLinq中Dispose()方法可能没有被实现(或者没有被正确地实现)。无论如何,听起来我需要在using块内执行所有将需要的操作特定于DataContext的操作。 - devuxer
@DanM:确实很有趣。我猜它在字面上的 SQL 意义上就像 Linq to SQL 一样。尽管如此,第三点可能是最重要且仍然有效的;调用 Dispose 不会对你造成任何伤害(嗯,除了 WCF),但是不经常调用它通常会造成问题。考虑到 DbLinq 代码库仍处于版本 0.2(即“不稳定”),如果未来的实现更改破坏了这里的错误但有效的用例,我不会感到惊讶。 - Aaronaught
哈哈,第二个最重要的点是我学到了如何正确使用DataContext对象。感谢您的帮助。 - devuxer

0
“persons”是一个IEnumerable集合,DataContext(存储库)仅需要进行.GetNew调用。
from/select等关键字是System.Linq命名空间中添加的扩展方法的语法糖。这些扩展方法添加了您在查询中使用的IEnumerable功能,而不是DataContext。实际上,您可以通过编程创建一个IEnumerable来演示所有这些操作,而无需使用LINQ2SQL。
如果您尝试使用这些对象进行任何进一步的存储库(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对象传递给它,这让我想到它必须负责查询数据库。然而,即使我处理它,查询似乎仍然被执行。这怎么可能? - devuxer
数据上下文是由Visual Studio工厂创建的,用于从数据库构建IEnumerables。实际上在查询中使用DataContext比在单独的方法中使用GetAll要容易得多。 - Sprague
你所进行的查询不是针对数据库的查询,而是针对你之前从数据库中获取并存储在内存中的对象集合的查询(就像你的示例一样)。 - Sprague
3
你的说法是不正确的,LINQ查询仅创建一个表达式树,LINQ to SQL 实现了一个 IQueryable 提供程序,将其转换为在数据库中执行的 SQL 查询。该查询并未立即执行,在枚举集合时从数据库中检索数据并加载到内存中。 - Ben Robinson

0

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