我花了很多时间查询数据库,然后根据查询构建对象集合。为了提高性能,我倾向于使用数据读取器,代码看起来像:
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"];