从DataReader检索数据的最有效方法是什么



我花了很多时间查询数据库,然后根据查询构建对象集合。为了提高性能,我倾向于使用数据读取器,代码看起来像:

while(rdr.Read()){
var myObj = new myObj();
myObj.Id = Int32.Parse(rdr["Id"].ToString();
//more populating of myObj from rdr
myObj.Created = (DateTime)rdr["Created"];
}

对于像DateTime这样的对象,我只是将rdr值强制转换为所需的类,但对于像int这样的值类型,这是不可能的,因此(IMHO)费力的ToString()后面跟着Int.Parse(...)

当然还有一种选择:

myObj.Id = rdr.GetInt32(rdr.GetOrdinal("Id"));

其看起来更干净并且不涉及对CCD_ 5的调用。

我和一位同事今天讨论了这个问题——他认为在上面的代码中两次访问rdr可能不如我以前的skool方式高效——有人能证实或否认这一点,并提出上面哪一种是best做这类事情的方式吗?我特别欢迎@JonSkeet;-)

我怀疑是否会有明显的性能差异,但只需将其从循环中取出,就可以避免对每一行进行名称查找。这可能是你能做到的最好的:

int idIdx = rdr.GetOrdinal("Id");
int createdIdx = rdr.GetOrdinal("Created");
while(rdr.Read())
{
var myObj = new myObj();
myObj.Id = rdr.GetFieldValue<int>(idIdx);
//more populating of myObj from rdr
myObj.Created = rdr.GetFieldValue<DateTime>(createdIdx);
}

实际上,使用SqlDataReader的方式在性能上存在差异,但它们在其他地方。即ExecuteReader方法接受CommandBehavior.SequentialAccess:

为DataReader提供一种处理包含具有大二进制值的列的行的方法。SequentialAccess使DataReader能够将数据作为流加载,而不是加载整行。然后,您可以使用GetBytes或GetChars方法指定开始读取操作的字节位置,以及返回数据的有限缓冲区大小。指定SequentialAccess时,要求按返回的顺序读取列,但不要求读取每一列。一旦您读取了返回数据流中的某个位置,就无法再从DataReader读取该位置或该位置之前的数据。使用OleDbDataReader时,您可以重新读取当前列值,直到读取超过该值。使用SqlDataReader时只能读取一次列值。

如果您确实使用大的二进制值,则差异非常小。获取字符串和解析是次优的,这是真的,因为NULL,所以最好使用rdr.SqlInt32(column)而不是GetInt32()来获取值。但这种差异在大多数应用程序上都不应该明显,因为你的应用程序实际上只做其他,只读取巨大的数据集。大多数应用程序的行为都不是这样的。在99.9999%的情况下,专注于优化数据库调用本身(即快速执行查询)将获得更大的好处。

我通常会为此引入一个RecordSet类:

public class MyObjRecordSet
{
private readonly IDataReader InnerDataReader;
private readonly int OrdinalId;
private readonly int OrdinalCreated;
public MyObjRecordSet(IDataReader dataReader)
{
this.InnerDataReader = dataReader;
this.OrdinalId = dataReader.GetOrdinal("Id");
this.OrdinalCreated = dataReader.GetOrdinal("Created");
}
public int Id
{
get
{
return this.InnerDataReader.GetInt32(this.OrdinalId);
}
}
public DateTime Created
{
get
{
return this.InnerDataReader.GetDateTime(this.OrdinalCreated);
}
}
public MyObj ToObject()
{
return new MyObj
{
Id = this.Id,
Created = this.Created
};
}
public static IEnumerable<MyObj> ReadAll(IDataReader dataReader)
{
MyObjRecordSet recordSet = new MyObjRecordSet(dataReader);
while (dataReader.Read())
{
yield return recordSet.ToObject();
}
}
}

用法示例:

List<MyObj> myObjects = MyObjRecordSet.ReadAll(rdr).ToList();

这对读者来说是最有意义的。无论它是最"高效"的(你实际上是调用两个函数而不是一个,它都不会像强制转换然后调用一个函数那样重要)。理想情况下,如果不影响您的性能,您应该选择看起来更可读的选项。

var ordinal = rdr.GetOrdinal("Id");
var id = rdr.GetInt32(ordinal);
myObj.Id = id;

对于DateTime这样的对象,我只需将rdr值强制转换为所需的类,但对于int 这样的值类型,这是无法做到的

这不是真的:DateTime也是一种值类型,如果字段为预期类型且不为null,则以下两种操作方式相同:

myObj.Id = (int) rdr["Id"];
myObj.Created = (DateTime)rdr["Created"];

如果它对你不起作用,也许你正在读取的字段是NULL?或者不是必需的类型,在这种情况下,需要进行两次强制转换。例如,对于SQL NUMERIC字段,您可能需要:

myObj.Id = (int) (decimal) rdr["Id"];

相关内容

  • 没有找到相关文章

最新更新