vendredi 31 juillet 2015

while on IDataReader.Read doesn't work with yield return but foreach on reader does

This is a commonly seen ADO.NET pattern to retrieve data from database using a data reader, but strangely doesn't work.

Doesn't work:

public static IEnumerable<IDataRecord> SelectDataRecord<T>(string query, string connString)
                                                          where T : IDbConnection, new()
{
    using (var conn = new T())
    {
        using (var cmd = conn.CreateCommand())
        {
            cmd.CommandText = query;
            cmd.Connection.ConnectionString = connString;

            conn.Open();
            using (var reader = (DbDataReader)cmd.ExecuteReader())
            {
                // the main part
                while (reader.Read())
                {
                    yield return (IDataRecord)reader;
                }
            }
        }
    }

This does work:

public static IEnumerable<IDataRecord> SelectDataRecord<T>(string query, string connString)
                                                          where T : IDbConnection, new()
{
    using (var conn = new T())
    {
        using (var cmd = conn.CreateCommand())
        {
            cmd.CommandText = query;
            cmd.Connection.ConnectionString = connString;

            conn.Open();
            using (var reader = (DbDataReader)cmd.ExecuteReader())
            {
                // the main part
                foreach (var item in reader.Cast<IDataRecord>())
                {
                    yield return item;
                }
            }
        }
    }

The only relevant change I see is that in the first code the iterator is returned from while loop while in the second it is returned from a foreach loop.

I call it like:

var result = SelectDataRecord<SQLiteConnection>(query, connString).ToList();

I tried with SQLite .NET connector as well as MySQL connector. The result is the same, i.e. first approach fails, second one succeeds.

Exception

SQLite

An unhandled exception of type 'System.InvalidOperationException' occurred in System.Data.SQLite.dll. Additional information: No current row

MySQL

An unhandled exception of type 'System.Exception' occurred in MySql.Data.dll. Additional information: No current query in data reader

Is it because of the implementation differences between reader.Read and reader.GetEnumerator in the specific ADO.NET connectors? I couldn't see any noticeable difference when I checked the source of System.Data.SQLite project, GetEnumerator calls Read internally. I am assuming ideally in both cases the yield keyword prevents eager execution of the method and the loops have to be executed only once the enumerator is enumerated externally.

Aucun commentaire:

Enregistrer un commentaire