Why is it possible to enumerate a DbLinq query after calling Dispose() on the DataContext?
Asked Answered
S

3

8

Update - The answer is apparently that DbLinq doesn't implement Dispose() properly. D'oh!


The below is all sort of misleading - Bottom line: DbLinq is not (yet) equivalent to LinqToSql, as I assumed when I originally asked this question. Use it with caution!

I'm using the Repository Pattern with DbLinq. My repository objects implement IDisposable, and the Dispose() method does only thing--calls Dispose() on the DataContext. Whenever I use a repository, I wrap it in a using block, like this:

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

This method returns an IEnumerable<Person>, so if my understanding is correct, no querying of the database actually takes place until Enumerable<Person> is traversed (e.g., by converting it to a list or array or by using it in a foreach loop), as in this example:

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

In this example, Dispose() gets called immediately after setting persons, which is an IEnumerable<Person>, and that's the only time it gets called.

So, three questions:

  1. How does this work? How can a disposed DataContext still query the database for results after the DataContext has been disposed?
  2. What does Dispose() actually do?
  3. I've heard that it is not necessary (e.g., see this question) to dispose of a DataContext, but my impression was that it's not a bad idea. Is there any reason not to dispose of a DbLinq DataContext?
Sear answered 17/5, 2010 at 18:38 Comment(2)
What does the repository.GetAll() method do? What does it return?Lichee
@Eclipsed4utoo, good question. I commented the code.Sear
H
3

1 How does this work? How can a disposed DataContext still query the database for results after the DataContext has been disposed?

It doesn't work. There's something you're not showing us. I'm guessing that either your repository class doesn't dispose the DataContext properly/at the right time, or that you are perfunctorily writing ToList() at the end of every query, which completely negates the query transformation and deferred execution you normally get.

Try the following code in a test app, I guarantee you that it will throw an 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);
}

This is the simplest possible reproducible case, and it will always throw. On the other hand, if you write people = context.Person.ToList() instead, then the query results have already been enumerated inside the using block, which I'll bet is what's happening in your case.

2 What does Dispose() actually do?

Among other things, it sets a flag indicating that the DataContext is disposed, which is checked on every subsequent query and causes the DataContext to throw an ObjectDisposedException with the message Object name: 'DataContext accessed after Dispose.'.

It also closes the connection, if the DataContext opened it and left it open.

3 I've heard that it is not necessary (e.g., see this question) to dispose of a DataContext, but my impression was that it's not a bad idea. Is there any reason not to dispose of a LinqToSql DataContext?

It is necessary to Dispose the DataContext, as it is necessary to Dispose every other IDisposable. You could potentially leak connections if you fail to dispose the DataContext. You could also leak memory if any of the entities retrieved from the DataContext are kept alive, since the context maintains an internal identity cache for the unit-of-work pattern it implements. But even if none of this were the case, it is not your concern what the Dispose method does internally. Assume that it does something important.

IDisposable is a contract that says, "cleanup may not be automatic; you need to dispose me when you're finished." You have no guarantees of whether or not the object has its own finalizer that cleans up after you if you forget to Dispose. Implementations are subject to change, which is why it's not a good idea to rely on observed behaviour as opposed to explicit specifications.

The worst thing that can happen if you dispose an IDisposable with an empty Dispose method is that you waste a few CPU cycles. The worst thing that can happen if you fail to dispose an IDisposable with a non-trivial implementation it is that you leak resources. The choice here is obvious; if you see an IDisposable, don't forget to dispose it.

Hysell answered 17/5, 2010 at 19:58 Comment(3)
Well I'm very glad I asked this question. And you also busted me on a bit of an oversimplification of my question. I'm technically not using LinqToSql, I'm using DbLinq (which is supposed to be "just like LinqToSql") and a SQLite database, and when I run your code, it doesn't throw any error. So, I'm now of the opinion that Dispose() is just not implemented on DbLinq (or not implemented properly). In any case, it sounds like I need to perform all operations that will be needed for a particular DataContext within the using block.Sear
@DanM: Interesting indeed. I guess it's LIKE Linq to SQL in the literal SQL sense of the word. :-) Nevertheless, point #3 is probably the most important and still valid; calling Dispose can never hurt you (well, except with WCF), but not calling it often will. Given that the DbLinq codebase is still at version 0.2 (i.e. not "stable"), I wouldn't be surprised if future implementation changes break the incorrect-but-working use case here.Hysell
Haha, the second most important point is that I learned something about the proper way to use a DataContext object. Thanks for your help.Sear
P
0

"persons" is an IEnumerable collection, the DataContext (repository) is only required to make the .GetNew call.

the from/select/etc keywords are syntactic sugar for extension methods added in the System.Linq namespace. These extension methods add the IEnumerable functionality you are using in your query, not the DataContext. In fact, you can do all of this without using LINQ2SQL at all, by programatically creating an IEnumerable to demonstrate.

If you attempt to make any further repository (DataContext) calls using these objects, that's when you'll receive an error.

The IEnumerable collection will contain ALL records from your repository, this is why you don't require the DataContext to make the query.

Extension methods: http://msdn.microsoft.com/en-us/library/bb383977.aspx

LINQ extension methods: http://msdn.microsoft.com/en-us/library/system.linq.enumerable_members.aspx

Preliminaries answered 17/5, 2010 at 18:49 Comment(4)
I understand about extension methods, and I use LINQ all the time (not just LinqToSql), but I guess I don't understand what the DataContext does exactly. The fact that I pass it a Connection object makes me think it must be responsible for querying the database. Yet, even if I dispose of it, queries still apparently get executed. How is that possible?Sear
The data context is factory created by Visual Studio to build IEnumerables from your database. It would be much easier for you to actually use the DataContext in your query, instead of using GetAll in a separate method.Preliminaries
The query you are making isn't a query against a database, it's a query against a collection of objects you got from the database and stored in memory earlier on (in your example).Preliminaries
you are incorrect, the linq query simply creates an expresion tree, linqtosql implements an iqueryable provider that turns this into a sql query that is executed in the database. This query is not executed and the data retrieved from the database into to memory until the collection is enumerated.Economizer
P
0

Deep inside the API, you will probably see a method using an api like this one:

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

When the command is executed, the associated Connection object is closed when the associated DataReader object is closed.

Pizor answered 17/5, 2010 at 19:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.