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

时间:2014-01-15 11:57:30

标签: c# performance sqldatareader data-access

我花了很多时间查询数据库,然后从查询中构建对象集合。为了性能,我倾向于使用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"));

看起来更干净,不涉及对ToString()的调用。

一位同事和我今天正在讨论这个问题 - 他建议在上面的代码中访问rdr两次可能效率低于我的旧skool方式 - 任何人都可以确认或否认这一点并建议以上哪一项这种事情的best方式是什么?我特别欢迎来自@JonSkeet的答案; - )

5 个答案:

答案 0 :(得分:3)

我怀疑会有一个非常明显的性能差异,但你可以简单地通过将其提升出循环来避免每行上的名称查找。这可能是您能够实现的最佳目标:

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);
}

答案 1 :(得分:2)

我通常会为此目的引入一个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();

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

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

  

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

如果你使用大二进制值,那么它就会产生很小的差别。获取字符串和解析不是最理想的,因为rdr.SqlInt32(column),最好使用NULL而不是GetInt32()来获取值。但是在大多数应用程序中差异不应该是显而易见的,unles你的应用程序正在做什么没有别的但是阅读庞大的数据集。大多数应用程序都不会那样。专注于优化数据库调用本身(即快速执行查询)将获得99.9999%的更多好处。

答案 4 :(得分:1)

  

对于像DateTime这样的对象,我只是将rdr值转换为所需的类,但是对于像int这样的值类型不能这样做

事实并非如此:DateTime也是一种值类型,如果字段属于预期类型且不为null,则以下两种方式都以相同的方式工作:

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

如果它不适合你,你正在阅读的字段可能是NULL吗?或者不是必需的类型,在这种情况下你需要施放两次。例如。对于SQL NUMERIC字段,您可能需要:

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