我能否从一个DataReader创建一个匿名类型集合?

9

我有这段代码:

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);
}

我想将行提取到一个匿名类型集合中,通常我会使用LINQ进行迭代,但由于每次需要调用.Read()来获取下一行,所以我不确定是否可能。
有什么方法可以做到这一点吗?
4个回答

8

您可以创建帮助器泛型方法,并让编译器推断类型参数:

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

用法:

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

你甚至可以将该方法作为DbDataReader的扩展方法:

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

然后像这样使用:

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

2
DbDataReader 实现了 IEnumerable,因此您实际上不需要定义自己的 Select,如果这是您的目标,您可以直接使用 Cast 然后使用 LINQ select。 - Servy
1
记录一下,DbDataReader 需要转换为 IDataRecordvar values = dataReader.Cast<IDataRecord>().Select(r => new { PropertyOne = r["ValueOne"].TryCastOrParse<string>(), // etc. }); - Mark Avenius
@MarkAvenius,关于SqlDataReader.Cast<IDataRecord>()的提示非常好,谢谢! - symbiont

5
这是一个动态实现的示例(我认为更易于操作),但有些人可能会认为它不符合你问题的字面意思。
像这样调用它:
var result = SelectIntoList("SELECT * FROM Cats",sqlconnectionString);

你可以像我一样,将它放入一个静态类中,并放在一个单独的文件中,以便更轻松地进行维护。
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();
    }
  }
}

“dynamic”?“ExpandoObject”?我可以告诉你,这会进行很多运行时检查,而且速度不会很快。 - MarcinJuraszek
这不是一个匿名类型的集合。ExpandoObject 是一个命名类型。(当然,是一个特殊的类型,但仍然不是匿名类型。) - Servy
@MarcinJuraszek - 不要太肯定,对我来说性能一直很好,这基本上就是SO构建的内容,参见https://github.com/SamSaffron/dapper-dot-net,与任何数据库访问相比,这些检查所需的时间相对较短。 - Hogan
远不止于性能问题,(并且从技术上讲并未回答这个问题),我个人更关心的是失去了编译时类型检查。作为开发者,这会更加难以处理,因为在使用序列时很容易输错列名,或者不记得所有类型是什么等等。 - Servy
@Servy - 我有另一个版本,其中我传递一个对象来匹配读取对象,以进行类型检查,我主要用于快速测试、原型设计、POC和一次性转换代码,当我需要快速完成某些事情时。但它确实有效,并且回答了问题的意图。我编辑了答案,指出了它与您和MarcinJuraszek的不同之处。 - Hogan

3

这是可行的,尽管不太整洁。首先,我们需要创建一个新方法,它可以让我们创建一个允许类型推断的空序列,并以虚拟值为基础:

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

这使我们能够创建一个匿名类型的列表:

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

这里的项目没有被使用。

现在我们可以将匿名类型添加到此列表中:

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),
    });
}

当然,使用命名类型可能会更容易,除非有真正的强烈理由不这样做。特别是如果您计划在创建列表范围之外使用它,则更应该如此。

谢谢,也许使用命名类型或元组会更整洁。虽然我很感激这些答案,但这是聪明的东西!我一定会记下来 :) - NibblyPig
@SLC 我猜你是指“元组”,而不是“Tuplet”。正如我在答案中所说,一个命名类型可能更可取。 - Servy
哎呀,没错!我同意。 - NibblyPig

0

从技术上讲,这可能并不能回答你的问题,但是不要使用读取器。相反,如果可以,请使用SqlDataAdapter填充一个DataSet。从该DataSet的第0个表中选择一个新的匿名对象,并从行集合中选择。

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();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接