【问题标题】:Return sql query results with deferred execution延迟执行返回sql查询结果
【发布时间】:2018-12-31 00:47:12
【问题描述】:

我有一个执行 SQL 语句的方法,它返回我的类 ResultSet 的实例,其中包含返回的行和列。我只想在调用者实际迭代结果时逐行读取数据。

public class ResultSet {
    public IEnumerable<Row> Rows {
        get;
        private set;
    }
    public ResultSet(IEnumerable<Row> rows, IEnumerable<Column> columns) {
        Rows = rows;
        // columns and other code omitted
    }
}

为此,我尝试将 IEnumerable 传递给我的类的构造函数,但问题是 DbDataReader 实例在您尝试迭代 Rows 属性时已经被释放ResultSet 实例:

    public ResultSet Execute(string sql) {
        using (var command = Connection.CreateCommand()) {
            command.CommandText = sql;

            var reader = command.ExecuteReader();
            try {
                IEnumerable<Row> MyFunc()
                {
                    while (reader.Read())
                        yield return new Row(reader);
                }
                var columns = GetColums(...);
                return new ResultSet(MyFunc(), columns);
            } finally {
                reader.Dispose();
            }
        }
    }

我知道我可以将 DbDataReader 实例传递给我的班级,而不是在 Execute 方法中处理它,但是我必须将 ResultSet 设为一次性,如果可能的话,我想避免这种情况。我不确定我想要做什么?

我看过yield return statement inside a using() { } block Disposes before executing,但不完全一样,因为它不涉及一次性资源。

【问题讨论】:

  • 创建一个自定义 IEnumerable,在调用 GetEnumerator 时调用 ExecuteReader,而不是更早。 (迭代器方法也可以返回IEnumerator,所以这并不像听起来那么麻烦。)或者,总是可以选择通过这种手动ORM代码并使用Dapper,它往往适合这类场景很好。
  • 或者创建一个自定义IEnumerable&lt;Row&gt; 构建自定义IEnumerator&lt;Row&gt;,在第一次调用MoveNext 时执行查询。
  • 读取器已经逐行读取数据(除非它是一小组记录,在这种情况下,它会在内部一次性读取它们)那么为什么需要使用yield? (免责声明:我不喜欢yield)。
  • 要么推迟整个请求(行和列),要么需要 2 个请求(一个用于急切加载列,一个用于延迟加载行)。你喜欢什么?
  • @Spotted 你说得对,现在我想起来了。我想我会选择返回 DbDataReader 实例,并为其提供 GetRows 和 GetColumns 的扩展方法。

标签: c# idisposable yield deferred-execution dbdatareader


【解决方案1】:

为了说明我的评论:

public class Row
{
    public Row(IDataReader reader)
    { }
};

public class RowEnumerator : IEnumerator<Row>
{
    public RowEnumerator(IDbConnection connection, string SQL)
    {
        _command = connection.CreateCommand();
        _command.CommandText = SQL;

        _reader = _command.ExecuteReader();
    }
    private readonly IDbCommand _command;
    private readonly IDataReader _reader;

    public Row Current => new Row(_reader);

    object IEnumerator.Current => Current;

    public bool MoveNext() => _reader.Read();

    public void Reset() => throw new NotImplementedException();

    public void Dispose()
    {
        _reader.Dispose();
        _command.Dispose();
    }
}

public class RowEnumerable : IEnumerable<Row>
{
    public RowEnumerable(IDbConnection connection, string SQL)
    {
        _connection = connection;
        _SQL = SQL;
    }
    private readonly IDbConnection _connection;
    private readonly string _SQL;

    public IEnumerator<Row> GetEnumerator() => new RowEnumerator(_connection, _SQL);

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

查询在创建RowEnumerator 时执行,即在调用RowEnumerable.GetEnumerator 时。

如果在foreach 循环中使用可枚举,则枚举数以及命令和读取器将被自动释放。否则你必须手动处理它。

【讨论】:

  • 谢谢,你说得对,这是一种方法。在我的情况下,我认为最简单的方法是返回 DbDataReader 实例并在其上为 GetRows 和 GetColumns 提供几个扩展方法。
猜你喜欢
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 2016-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-08
  • 1970-01-01
相关资源
最近更新 更多