ado.net中的DataReader似乎没有Peek
方法。我希望能够在循环阅读器之前执行一些一次性处理,能够查看第一行中的数据而不会导致后续迭代跳过它会很好。实现这一目标的最佳方法是什么?
我使用的是SqlDataReader
,但最好是实施尽可能一般(即适用于IDataReader
或DbDataReader
)。
答案 0 :(得分:5)
我会建议类似于Jason的解决方案,但使用实现IDataReader的包装器,所以:
sealed public class PeekDataReader : IDataReader
{
private IDataReader wrappedReader;
private bool wasPeeked;
private bool lastResult;
public PeekDataReader(IDataReader wrappedReader)
{
this.wrappedReader = wrappedReader;
}
public bool Peek()
{
// If the previous operation was a peek, do not move...
if (this.wasPeeked)
return this.lastResult;
// This is the first peek for the current position, so read and tag
bool result = Read();
this.wasPeeked = true;
return result;
}
public bool Read()
{
// If last operation was a peek, do not actually read
if (this.wasPeeked)
{
this.wasPeeked = false;
return this.lastResult;
}
// Remember the result for any subsequent peeks
this.lastResult = this.wrappedReader.Read();
return this.lastResult;
}
public bool NextResult()
{
this.wasPeeked = false;
return this.wrappedReader.NextResult();
}
// Add pass-through operations for all other IDataReader methods
// that simply call on 'this.wrappedReader'
}
请注意,对于所有未受影响的属性,这确实需要相当多的传递代码,但好处是它是一个通用抽象,可以“窥视”结果集中的任何位置,而无需在后续步骤中向前移动'读'操作。
使用:
using (IDataReader reader = new PeekDataReader(/* actual reader */))
{
if (reader.Peek())
{
// perform some operations on the first row if it exists...
}
while (reader.Read())
{
// re-use the first row, and then read the remainder...
}
}
请注意,如果前一个操作不是'Peek()',那么每个'Peek()'调用实际上都会移动到下一个记录。使用'Read()'操作保持这种对称性可以提供更简单的实现和更优雅的API。
答案 1 :(得分:4)
您可以创建一个跟踪peek-mode与regular-mode的状态机。也许这样的事情(可以把它们全部扔进一个名为Peeker.cs的文件或者类似的东西):
public sealed class Peeker
{
internal readonly PeekMode PEEKING;
internal readonly NormalMode NORMAL;
private ReadState _state;
public Peeker()
{
PEEKING = new PeekMode(this);
NORMAL = new NormalMode(this);
// Start with a normal mode
_state = NORMAL;
}
public object[] OnRead(IDataReader dr, bool peek)
{
return _state.OnRead(dr, peek);
}
internal void SetState(ReadState state)
{
_state = state;
}
}
internal abstract class ReadState
{
protected Peeker _peeker;
protected ReadState(Peeker p)
{
_peeker = p;
}
public abstract object[] OnRead(IDataReader dr, bool peek);
}
internal class PeekMode : ReadState
{
public PeekMode(Peeker p)
: base(p)
{
}
public override object[] OnRead(IDataReader dr, bool peek)
{
object[] datarow = new object[dr.FieldCount];
if (peek)
{
dr.GetValues(datarow);
}
else
{
if (dr.Read())
{
dr.GetValues(datarow);
_peeker.SetState(_peeker.NORMAL);
}
}
return datarow;
}
}
internal class NormalMode : ReadState
{
public NormalMode(Peeker p)
: base(p)
{
}
public override object[] OnRead(IDataReader dr, bool peek)
{
object[] datarow = new object[dr.FieldCount];
if (peek)
{
if (dr.Read())
{
dr.GetValues(datarow);
_peeker.SetState(_peeker.PEEKING);
}
}
else
{
if (dr.Read())
{
dr.GetValues(datarow);
}
}
return datarow;
}
}
有点矫枉过正,但是很好。
要使用它,您只需执行以下操作:
Peeker p = new Peeker();
.
.
.
SomeDataReaderType dr = SomeCommandType.ExecuteReader();
.
.
.
// To peek
object[] myDataRow = p.OnRead(dr, true);
// or not to peek
object[] myDataRow = p.OnRead(dr, false);
然后执行您对行所需的操作。可能有一种比使用对象数组更好的方法,但是你明白了。
祝你好运!答案 2 :(得分:3)
您不需要Peek()方法。您可以使用Do While循环完成所需的操作。
所以而不是
while(dr.read())
{
... do stuff
}
你会
dr.read();
... do stuff
do
{
... do stuff
}while(dr.read())