【发布时间】:2011-01-26 01:22:45
【问题描述】:
如果您只需要显示数据而不需要操作数据,则 DataReader 比 DataTable 更有效。但是,要从数据访问层获取 DataReader,我应该让连接对象保持打开状态吗?我觉得这也是一个很大的效率问题。那么是否有另一种方法可以充分利用 DataReader?
【问题讨论】:
如果您只需要显示数据而不需要操作数据,则 DataReader 比 DataTable 更有效。但是,要从数据访问层获取 DataReader,我应该让连接对象保持打开状态吗?我觉得这也是一个很大的效率问题。那么是否有另一种方法可以充分利用 DataReader?
【问题讨论】:
如果你想完全抽象ADO.NET的连接和仪式,DataReader是一个小挑战。我真的不喜欢我的数据工具有一个松散的开放连接,希望 DataReader 已被处理(假设您使用了选项CommandBehavior.CloseConnection)。此外,当使用大量 DataReader 时,很难对连接进行池化,因为在关闭之前的 DataReader 之前您无法对连接进行任何操作。它们不能轻易被传递。您的数据工具不是真正的抽象。
另一方面,DataTables 非常灵活,可以编写一些非常高效、清晰的代码。 Linq-To-DataTable 很棒。幸运的是,DataTable 是actually pretty efficient。对于非巨大的结果集,它几乎与数据读取器一样快。 (这当然取决于你在做什么。)我越来越多地从我的数据工具而不是阅读器中获取数据表。它真的让生活变得简单。我可以继续使用相同的打开连接。数据工具中没有“状态”。
获取 DataReader 的代码非常简单。所以当我真的需要一个 DataReader 时(不经常),我只是让我的 DAL 把我的连接交给我,然后我自己得到我的 DataReader。
【讨论】:
是的,数据读取器绝对是最高效的 - 但您确实不希望长时间保持连接打开!
DataReader 将您的数据读入实体对象;打开连接,读取数据,关闭连接这可能是你能得到的最高效的方法——它需要一些工作,一些无聊的代码,等等,但它的速度差不多。
如果您对开发人员的工作效率比原始速度更感兴趣,为什么不使用某种 ORM 来完成所有这些无聊、烦人的来回映射呢?为您节省大量编码和需要维护的杂乱东西!
【讨论】:
让您的数据层返回对象,而不是数据表或数据读取器。我建议您使用数据阅读器填充您的对象。
【讨论】:
我通常做的是用 CommandBehavior.CloseConnection 打开阅读器。然后我运行阅读器并将数据读入我自己的对象模型或列表或内存中的任何数据,然后关闭阅读器。它的功能与数据表大致相同,但我只是讨厌处理臃肿且类型松散的数据结构。
【讨论】:
当我之前研究这个时,我相信我发现 DataReader 和 DataTable 之间的性能差异是微不足道的,除了可能非常大量的数据。从那以后,我通常使用 DataTable,因为它功能更全面,可以在断开连接的情况下工作,等等。
【讨论】:
我从不让 DataReader 进入野外(在 DAL 之外)。在某处打开连接只是时间问题。此外,我几乎从来没有在一次调用中处理如此多的数据,在这种调用中传递 DataTable 或 DataSet 会带来问题。
我们使用的是面向对象的语言,而 DAL 可以真正利用这一点。您的项目中应该只有一行代码可以获取连接字符串。只有一个对象实际接触到数据库(调用 ExecuteNonQuery、DA.Fill() 等)
这还可以让您充分参与记录异常等,因为您只执行一次。因此,在我用于所有项目中的所有 DAL 对象的一个 DAL 基类中,我有这样的逻辑,即如果 DAL 引发异常,则它会记录到我数据库中的表中。如果数据库日志记录失败,此日志记录将故障转移到文本文件。
So the code I see a lot looks like:
- Start a try block
- Make a SQLCommand
- Get connection string.
- Make Connection object
- Open the connection
- Get the data
- Bind the data
- Close the connection
- Log error if exception
由于我封装了所有这些,我获取数据的代码现在看起来像:
GridView1.DataSource = cProgram.DB.getMyData();
(或者更可能是 BAL 对象位于两者之间)。 DB 是一个普通对象(不是静态的),但每个应用程序只实例化一次。
【讨论】:
让我们有一个愚蠢的基准,检查DataReader(.NET版本4)的速度是多少。 我从数据库中获取了一个记录(SQL Server 2000)并读取其所有字段。我重复了这个过程 1000 次。 DataReader拍摄了17.3327585秒,并且DataTable花了18.37320156,因此DataReader比LataTable快〜1.04秒,有1000个读取。
所以,如果DataTable优先于DataTable首选,则可以获得0.00104秒的性能增益。
【讨论】:
Straight from the documentation:
当您需要检索多行数据以便可以 以其他方式显示或处理数据,你有两个基本的 选择。您可以使用 DataSet 对象或 DataReader 对象。
DataReader 方法通常更快,因为它避免了 与创建 DataSet 对象相关的开销。这 与 DataSet 对象关联的开销包括创建 DataSet 子对象,例如 DataTables、DataRows 和 DataColumns。 但是,DataReader 提供的灵活性较低,并且不太适合 对于必须缓存数据并将数据传递到的情况 具有多层的应用程序中的组件。
注意:用于填充 DataSet 的 DataAdapter 在内部使用 DataReader。
在满足以下条件时使用 DataReader:
• 您需要以只读方式访问数据(消防水带 场景),并且您希望尽快访问数据,并且 你不需要缓存它。
• 您有一个数据容器,例如您可以使用的业务组件 把数据放进去。
当满足以下条件时使用 DataSet:
• 您必须在层之间缓存或传递数据。
• 您需要 XML 或数据的内存关系视图 非 XML 操作。
• 您想要更新部分或全部检索到的行,并且您想要 使用 SqlDataAdapter 类的批量更新工具。
• 您必须将数据绑定到 DataReader 不能的控件类型 一定要。许多能够进行数据绑定的 Windows 窗体控件 需要实现 IList 接口的数据源。数据集 实现 IList,但 DataReader 实现 IEnumerable。 IEnumerable 支持将数据绑定到大多数 Web 窗体控件,但不支持 某些 Windows 窗体控件。检查数据源要求 您要绑定的特定控件类型。
• 您必须同时访问多组数据,并且您 不想持有开放的服务器资源。
虽然谈到DataSet,但基本上大部分内容也适用于DataTable。从效率的角度来看,here is rare benchmarking from msdn itself。底线是DataReader 稍微快一点,如果重要的话..
See this related question 也提出了一些很酷的 ORM 和基准测试。
【讨论】:
这与我发布的 here 相同。
我用各种方法对自己进行了一些基准测试:
public DataTable Read1(string query)
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.Connection.Open();
var table = new DataTable();
using (var r = cmd.ExecuteReader())
table.Load(r);
return table;
}
}
public DataTable Read2<S>(string query) where S : IDbDataAdapter, IDisposable, new()
{
using (var da = new S())
{
using (da.SelectCommand = conn.CreateCommand())
{
da.SelectCommand.CommandText = query;
da.SelectCommand.Connection.Open();
DataSet ds = new DataSet();
da.Fill(ds);
return ds.Tables[0];
}
}
}
public IEnumerable<S> Read3<S>(string query, Func<IDataRecord, S> selector)
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.Connection.Open();
using (var r = cmd.ExecuteReader())
while (r.Read())
yield return selector(r);
}
}
public S[] Read4<S>(string query, Func<IDataRecord, S> selector)
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.Connection.Open();
using (var r = cmd.ExecuteReader())
return ((DbDataReader)r).Cast<IDataRecord>().Select(selector).ToArray();
}
}
public List<S> Read5<S>(string query, Func<IDataRecord, S> selector)
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = query;
cmd.Connection.Open();
using (var r = cmd.ExecuteReader())
{
var items = new List<S>();
while (r.Read())
items.Add(selector(r));
return items;
}
}
}
1 和 2 返回 DataTable 而其余的强类型结果集,所以它完全不是苹果对苹果,但我会相应地计时。
只是必需品:
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Read1(query); // ~8900 - 9200ms
Read1(query).Rows.Cast<DataRow>().Select(selector).ToArray(); // ~9000 - 9400ms
Read2<MySqlDataAdapter>(query); // ~1750 - 2000ms
Read2<MySqlDataAdapter>(query).Rows.Cast<DataRow>().Select(selector).ToArray(); // ~1850 - 2000ms
Read3(query, selector).ToArray(); // ~1550 - 1750ms
Read4(query, selector); // ~1550 - 1700ms
Read5(query, selector); // ~1550 - 1650ms
}
sw.Stop();
MessageBox.Show(sw.Elapsed.TotalMilliseconds.ToString());
查询返回大约 1200 行和 5 个字段(运行 100 次)。除了Read1,其他都表现不错。我更喜欢Read3,它会延迟返回数据,如枚举。如果您只需要枚举它,这对记忆很有帮助。要在内存中保存集合的副本,最好使用Read4 或Read5。
【讨论】: