0

我正面临一个非常奇怪的结构。一旦枚举结束,IEnumerable 中返回的 Foo 类型就会丢失其数据。这意味着我不能进行 enumeration.First() 因为数据会立即丢失。

对其进行循环有效,但因为我知道它只会包含一个奇怪的元素。

int Test(out int something)
    IEnumerable<Foo> enumeration = ...
    for (var foo in enumeration) {
        something = foo.GetSomething ();
        return foo.GetAnInt ();
    }
    something = 42;
    return 0;
}

我想到的另一种方法是滥用 Linq Select,但这同样可怕。

有没有办法解决这个限制?解决根本原因显然是优越的,但在这种情况下很难。

编辑:IEnumerable<IDataRecord>它来自yield returned事务 SQL 数据阅读器。

public IEnumerable<IDataRecord> ExecuteReader (SqlCommand cmd)
{
    using (var con = GetConnection()) {
        con.Open ();

        using (var tr = con.BeginTransaction ()) {
            cmd.Connection = con;

            var reader = cmd.ExecuteReader ();
            while (reader.Read ()) {
                yield return reader;
            }

            tr.Commit ();
        }
    }
}
4

3 回答 3

4

问题是您的ExecuteReader方法确实只是返回SqlDataReader本身(实现IDataRecord),而不是返回数据块。所以当你这样做时:

var list = ExecuteReader(...).ToList();

在这种情况下,列表的所有元素都将是同一个SqlDataReader实例,但在ToList执行之后,阅读器已关闭。我有点惊讶你没有得到ObjectDisposedException.

为此,您需要在IDataRecord. 您认为可以迭代数据记录中的元素。另一种选择是将其更改ExecuteReader为以下内容:

public IEnumerable<T> ExecuteReader<T>(SqlCommand cmd, 
    Func<IDataRecord, T> recordCreator)
{
    using (var con = GetConnection()) {
        con.Open ();

        using (var tr = con.BeginTransaction()) {
            cmd.Connection = con;

            var reader = cmd.ExecuteReader();
            while (reader.Read()) {
                yield return recordCreator(reader);
            }

            tr.Commit();
        }
    }
}

这样,您可以执行以下操作:

var list = ExecuteReader(command, record => new
{
    Item1 = record.GetInt("id"),
    Item2 = record.GetString("name")
});

注意:我不确定您为什么需要为此进行交易。

于 2012-05-09T09:41:45.310 回答
1

怎么样

int Test(out int something)
{
    IEnumerable<Foo> enumeration = ...
    var values = enumeration
        .Select(foo => new
                {
                    something = foo.GetSomething(),
                    anInt = foo.GetAnInt()
                })
        .FirstOrDefault();
    if (values != null)
    {
        something = values.something;
        return values.anInt;
    }
    else
    {
        something = 42;
        return 0;
    }
}

GetSomething并且GetAnInt在枚举内部被调用。

于 2012-05-09T09:25:29.060 回答
0

另一个想法可能是将方法的结果类型从 IEnumerable 转换为 IEnumerator。这样,范围控制就容易多了,而且返回单个结果也不需要任何(假)循环。

编辑:我想我找到了一种重构整个问题的方法。这通过使用包含以前在方法中找到的逻辑的新的一次性类来规避最初的问题。它非常易读,甚至更少的代码。

public TransactedConnection GetConnection (string text)
{
    return new TransactedConnection (_ConnectionString, text);
}

public class TransactedConnection : IDisposable
{
    private readonly SQLiteCommand _Cmd;
    private readonly SQLiteConnection _Con;
    private readonly SQLiteTransaction _Tr;

    public TransactedConnection (string connection, string text)
    {
        _Cmd = new SQLiteCommand (text);
        _Con = new SQLiteConnection (connection);
        _Con.Open ();
        _Cmd.Connection = _Con;
        _Tr = _Con.BeginTransaction ();
    }

    public void Dispose ()
    {
        _Tr.Commit ();
        _Tr.Dispose ();
        _Con.Dispose ();
        _Cmd.Dispose ();
    }

    public SQLiteParameterCollection Parameters
    {
        get
        {
            return _Cmd.Parameters;
        }
    }

    public int ExecuteNonQuery ()
    {
        return _Cmd.ExecuteNonQuery ();
    }

    public object ExecuteScalar ()
    {
        return _Cmd.ExecuteScalar ();
    }

    public SQLiteDataReader ExecuteReader ()
    {
        return _Cmd.ExecuteReader ();
    }
}

public void Test (string match)
{
    var text = "SELECT * FROM Data WHERE foo=@bar;";

    using (var cmd = GetConnection (text)) {
        cmd.Parameters.Add ("@bar", DbType.String).Value = match;

        using (var reader = cmd.ExecuteReader ()) {
            while (reader.Read ()) {
                Console.WriteLine (reader["foo"]);
            }
        }
    }
}
于 2012-05-09T13:34:26.757 回答