Question

I have this code:

var query = "SELECT * FROM Cats";

var conn = new SqlConnection(sqlConnectionString);

conn.Open();

var cmd = new SqlCommand(query);
var reader = cmd.ExecuteReader();

while (reader.Read())
{
    var CatName = reader.GetString(0);
    var CatDOB = reader.GetDateTime(1);
    var CatStatus = reader.GetInt32(2);
}

I'd like to pull the rows out into an anonymous type collection, which I'd normally do using LINQ to iterate, but I an not sure if it's possible due to the way you have to call .Read() each time to get the next row.

Is there a way to do this?

Was it helpful?

Solution

You can create helper generic method and let compiler infer type parameter:

private IEnumerable<T> Select<T>(DbDataReader reader, Func<DbDataReader, T> selector)
{
    while(reader.Read())
    {
        yield return selector(reader);
    }
}

usage:

var items = SelectFromReader(reader, r => new { CatName = r.GetString(0), CarDOB = r.GetDateTime(1), CatStatus = r.GetInt32(2) });

You can even make the method an extension method on DbDataReader:

public static IEnumerable<T> Select<T>(this DbDataReader reader, Func<DbDataReader, T> selector)
{
    while (reader.Read())
    {
        yield return selector(reader);
    }
}

and use it like that:

var items = reader.Select(r => new { CatName = r.GetString(0), CarDOB = r.GetDateTime(1), CatStatus = r.GetInt32(2) });

OTHER TIPS

Here is an example of doing it with dynamic (which I think is easier to work with) but some may feel does not adhere to the letter of your question.

Call it like this:

var result = SelectIntoList("SELECT * FROM Cats",sqlconnectionString);

You could (like I did) put it into a static class in a separate file for easier maintanence.

public static IEnumerable<dynamic> SelectIntoList(string SQLselect, string connectionString, CommandType cType = CommandType.Text)
{
  using (SqlConnection conn = new SqlConnection(connectionString))
  {
    using (SqlCommand cmd = conn.CreateCommand())
    {
      cmd.CommandType = cType;
      cmd.CommandText = SQLselect;

      conn.Open();

      using (SqlDataReader reader = cmd.ExecuteReader())
      {

        if (reader.Read())  // read the first one to get the columns collection
        {
          var cols = reader.GetSchemaTable()
                       .Rows
                       .OfType<DataRow>()
                       .Select(r => r["ColumnName"]);

          do
          {
            dynamic t = new System.Dynamic.ExpandoObject();

            foreach (string col in cols)
            {
              ((IDictionary<System.String, System.Object>)t)[col] = reader[col];
            }

            yield return t;
          } while (reader.Read());
        }
      }

      conn.Close();
    }
  }
}

It's possible, although not particularly neat. We'll need to create a new method that will allow us to create an empty sequence that allows for type inference off of a dummy value for starters:

public static IEnumerable<T> Empty<T>(T dummyValue)
{
    return Enumerable.Empty<T>();
}

This lets us create a list of an anonymous type:

var list = Empty(new
{
    CatName = "",
    CatDOB = DateTime.Today,
    CatStatus = 0
}).ToList();

(The item here isn't used.)

Now we can add our anonymous types to this list:

var cmd = new SqlCommand(query);
var reader = cmd.ExecuteReader();

while (reader.Read())
{
    list.Add(new
    {
        CatName = reader.GetString(0),
        CatDOB = reader.GetDateTime(1),
        CatStatus = reader.GetInt32(2),
    });
}

Of course, using a named type would likely be easier, so I would suggest using one unless there is a real compelling reason not to do so. That is especially true if you plan to use the list outside of the scope it's created in.

Technically, it may not answer your question, but simply don't use a reader. Instead use a SqlDataAdapter to Fill a DataSet, if you can. Take the 0th Table of that DataSet, and select a new anonymous object from the Rows collection.

using System.Data; // and project must reference System.Data.DataSetExtensions

var ds = new DataSet();
using (var conn = DbContext.Database.GetDbConnection())
using (var cmd = conn.CreateCommand())
{
  cmd.CommandType = CommandType.Text;
  cmd.CommandText = sqlText;
  conn.Open();
  (new SqlDataAdapter(cmd)).Fill(ds);
}
var rows = ds.Tables[0].AsEnumerable(); // AsEnumerable() is the extension
var anons = rows
  .Select(r => new { Val = r["Val"] })
  .ToList();
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top