I have resolved the problem. Good news, Robert: it's not a bug in your code.
Remember when I said that it's very unlikely we have a bug in our wrapper code that made sure that Dispose was called? Well, I was right, but only partially. There was a subtle bug in another piece of code that used the wrapper.
Allow me to sketch the problem. If you're not a fan of LINQ and extension methods, feel free to skip the rest of this post.
So, our wrapper looks like this:
void ExecuteQuery(...) {
using(DbCommand = ...) {
using(DbDataReader reader = ...) {
while (reader.Read())
yield return ...;
}
}
}It looks fine, doesn't it? Two usings, both making sure that command and data reader are disposed. This code is being called from many, many places in our app and works great. But in this particular case, instead of calling foreach on the result of this method (or something similar), I called an extension method FirstOrDefault. Since our app is still .NET 2.0, we have to develop our own IEnumerable extension methods, can't use the ones from System.Core. FirstOrDefault (look it up in MSDN) should return first element from a sequence, or default(T) for some type T. Quite a useful method, in fact. In this case, if I get back default (null for reference types), then there are no elements in the sequence (that is, query returned 0 rows), but if there is one, I get that back.
Unfortunately, there was a bug in that method (our implementation of that method, .NET framework version is correct). I forgot to dispose the Enumerator, thus the "loop" would never end, thus the Dispose would not get called on data reader, hence the exception.
Phew, glad it's over. Not only that I've fixed the problem in this particular case, but also many, many yet unseen problems in God know how many other places will be avoided.
Btw Robert, thanks for a complete implementation of ADO.NET interfaces. I was able to completely (more or less, there are differences in the SQL code unfortunately) isolate our storage related code from the specifics of the concrete DB implementation (as you can see in the snippet above, we use DbXXX classes everywhere).
Regards,
Drazen