While working on some sample code for my upcoming LINQ for Mobile Devices Webcast (Wednesday 27-Feb-2008) that uses SqlCeResultSets, my sample unexpectedly crashed with a stack overflow while running the following
using (SqlCeConnection conn = new SqlCeConnection("Data Source = " + NorthwindDatabaseFullPath))
{
conn.Open();
using (SqlCeCommand command = new SqlCeCommand("Orders", conn))
{
command.CommandType = CommandType.TableDirect;
resultSet = command.ExecuteResultSet(ResultSetOptions.Scrollable | ResultSetOptions.Sensitive);
}
foreach(SqlCeUpdatableRecord rec in resultSet)
{
// ...
// ...
}
}
That's some pretty basic SqlCeResult code so I wasn't expecting a problem. What was most surprising is on which statement the error was happening: the foreach statement. Knowing that a foreach loop is a simple encapsulation of iterating over an IEnumerator reference returned by a call to GetEnumerator, I decided to try calling GetEnumerator on the SqlCeResultSet directly.
IEnumerable<SqlCeUpdatableRecord> rsEnumerable = resultSet.GetEnumerator();
Again a Stack Overflow… So I decided to use Reflector and checkout the implementation of SqlCeResultSet.GetEnumerator().
public override IEnumerator GetEnumerator()
{
return this.GetEnumerator();
}
I was shocked to see this code … the method infinitely makes recursive calls to itself; a sure-fire way to cause a Stack Overflow.
Now I just couldn't believe that there's no way to retrieve an IEnumerator reference from a SqlCeResultSet. In fact, we know that it's possible because we can use the Cast<T> extension method.
IEnumerable<SqlCeUpdatableRecord> rsEnumerable = resultSet.Cast<SqlCeUpdatableRecord>();
The fact that the Cast<T> extension method works is our key to understanding what's actually happening. The Cast<T> extension method internally stores the reference to the SqlCeResultSet as an IEnumerable reference which means that it retrieves the class' enumerator by calling IEnumerable.GetEnumerator rather than SqlCeResultSet.GetEnumerator. In other words, it's explicitly calling the IEnumerable interfaces GetEnumerator rather than the SqlCeResultSet's public GetEnumerator method.
If we go back to Reflector, we'll see that SqlCeResultSet has an explicit IEnumerable.GetEnumerator method implementation in addition to the public GetEnumerator method. The IEnumerable.GetEnumerable method is implemented as follows…
IEnumerator
IEnumerable.GetEnumerator()
{
ResultSetEnumerator enumerator = new ResultSetEnumerator(this);
enumerator.Reset();
return enumerator;
}
Now that's what we expected.
The Stack Overflow error occurs because the public GetEnumartor() method is calling back to itself rather than calling its IEnumerable.GetEnumerator method.
So what does this all mean? Until a fix is released, anytime you write any code that uses the SqlCeResultSet enumerator, you need to explicitly cast the SqlCeResultSet reference to IEnumerable.
To fix our foreach loop we can do this…
IEnumerable resultSetEnumerable = (IEnumerable)resultSet;
foreach (SqlCeUpdatableRecord rec in resultSetEnumerable { ... }
Or simply…
foreach (SqlCeUpdatableRecord rec in (IEnumerable)resultSet) { ... }
Note: This issue occurs on both the desktop and device versions of SQL Server Compact 3.5. Presumably this issue will no longer exist once a Service Pack for SSC 3.5 is released.
Posted
Feb 22 2008, 09:31 AM
by
jim-wilson