【问题标题】:ADO.NET data table vs. data readerADO.NET 数据表与数据读取器
【发布时间】:2011-01-26 01:22:45
【问题描述】:

如果您只需要显示数据而不需要操作数据,则 DataReader 比 DataTable 更有效。但是,要从数据访问层获取 DataReader,我应该让连接对象保持打开状态吗?我觉得这也是一个很大的效率问题。那么是否有另一种方法可以充分利用 DataReader?

【问题讨论】:

    标签: .net asp.net ado.net


    【解决方案1】:

    如果你想完全抽象ADO.NET的连接和仪式,DataReader是一个小挑战。我真的不喜欢我的数据工具有一个松散的开放连接,希望 DataReader 已被处理(假设您使用了选项CommandBehavior.CloseConnection)。此外,当使用大量 DataReader 时,很难对连接进行池化,因为在关闭之前的 DataReader 之前您无法对连接进行任何操作。它们不能轻易被传递。您的数据工具不是真正的抽象。

    另一方面,DataTables 非常灵活,可以编写一些非常高效、清晰的代码。 Linq-To-DataTable 很棒。幸运的是,DataTable 是actually pretty efficient。对于非巨大的结果集,它几乎与数据读取器一样快。 (这当然取决于你在做什么。)我越来越多地从我的数据工具而不是阅读器中获取数据表。它真的让生活变得简单。我可以继续使用相同的打开连接。数据工具中没有“状态”。

    获取 DataReader 的代码非常简单。所以当我真的需要一个 DataReader 时(不经常),我只是让我的 DAL 把我的连接交给我,然后我自己得到我的 DataReader。

    【讨论】:

    • DataTables 是一种返回数据的惰性方式,会导致代码无法维护。您应该使用 Readers 并返回对象。
    • @Keith Rousseau,看起来我们会不同意。对于实例化一个或少量对象,数据表提供了简单性和灵活性。也许您希望您的数据为 XML?从DataTable,没问题。您从 DataReaders 获得的效率通常很小而且不值得。
    • 我可以举出很多例子。使用 DataRows,您可以在构建对象时拥有出色的灵活性;您可以从一个 db-query 和一个 LINQ 语句构造大量对象。您从 DataReaders 获得的效率通常很小且不值得。如果 lazy 你的意思是用 small 数量的代码构建许多对象,这些代码非常清晰,并且初级程序员不可能让连接保持打开状态,那么是的,我很懒惰。我希望它快速、清晰、没有问题地完成。
    • 我对 DataTables 的问题与效率无关。它是关于代码可维护性和魔术字符串的使用。你失去了强类型,这是你从数据访问层返回的对象中获得的。这使得代码更难维护、扩展和调试。
    • 我不太明白你来自哪里。当然,您在业务逻辑和 UI 中使用强类型对象。我的 ADO.NET 工作仅用于将数据放入对象中,听起来您也是如此。但是在某些时候,无论是在工厂方法中还是在您的对象内部或任何地方,您都会从数据库中获取数据。这个问题是关于,如果你使用一个工具来抽象出你的 ASP.NET 工作,是使用 DataReaders 还是 DataTables。当我将数据铲入对象以供更高层使用时,我发现 DataTables 通常更好。
    【解决方案2】:

    是的,数据读取器绝对是最高效的 - 但您确实希望长时间保持连接打开!

    • 使用DataReader 将您的数据读入实体对象;打开连接,读取数据,关闭连接
    • 对您的业务对象做任何您需要做的事情
    • 将更改存储回来,例如通过使用即席 SQL 查询、存储过程或您想要的任何其他方式;再次:打开连接,写回更改,关闭连接

    这可能是你能得到的最高效的方法——它需要一些工作,一些无聊的代码,等等,但它的速度差不多。

    如果您对开发人员的工作效率比原始速度更感兴趣,为什么不使用某种 ORM 来完成所有这些无聊、烦人的来回映射呢?为节省大量编码和需要维护的杂乱东西!

    【讨论】:

    • 感谢您的回答。我通常使用 ORM 或使用我的实体对象创建我的自定义业务逻辑层。但是,对于一个小项目,我尝试只使用数据阅读器。除了 orm 或 entity 对象,我还能做什么?
    • @erasmus 如果它真的是一个小项目,不要担心效率。只需使用对您来说最简单的方法。没有规定每个应用程序都需要尽可能好地执行。唯一的期望是它的性能与预期的需求一样好。
    • @erasmus:同意 Mike 的观点——项目越小,最后一点性能就越不重要。使用 Linq-to-SQL、NHibernate 或其他东西 - 它只是 非常 更容易完成工作,这在小型项目中绝对更重要
    • @marc_s,很好的解释。谢谢。
    【解决方案3】:

    让您的数据层返回对象,而不是数据表或数据读取器。我建议您使用数据阅读器填充您的对象。

    【讨论】:

    • 我的视图层可以知道我的域对象吗?
    • 如果你的意思是实体,这里没有域对象。
    • @jim - 这是一个完全独立的问题,但答案通常是否定的。
    • @jim - 您的视图层不应依赖于构建数据层的表的特定结构。但是,如果“域对象”是指业务对象(或“模型”),那么,是的,这些是 UI 和 BLL 之间通信的通用语言。现在,您会发现很多情况下,从数据对象到模型(域对象)的 映射 非常简单,您会很想跳过手续,但不要 - 你会很高兴你把它们分开了。
    • @Keith, @erasmus - 你对域对象的定义是什么?据我所知,在人工智能社区和软件中,“领域对象”一词非常具体地指的是世界上的对象——我们用来思考、计算或显示的对象。它们映射到数据库中的低级表示,但它们不是一回事。根据您的回答,您似乎将“域对象”视为类似于数据库记录的东西。
    【解决方案4】:

    我通常做的是用 CommandBehavior.CloseConnection 打开阅读器。然后我运行阅读器并将数据读入我自己的对象模型或列表或内存中的任何数据,然后关闭阅读器。它的功能与数据表大致相同,但我只是讨厌处理臃肿且类型松散的数据结构。

    【讨论】:

      【解决方案5】:

      当我之前研究这个时,我相信我发现 DataReader 和 DataTable 之间的性能差异是微不足道的,除了可能非常大量的数据。从那以后,我通常使用 DataTable,因为它功能更全面,可以在断开连接的情况下工作,等等。

      【讨论】:

      • 结果如何?你能给我们吗?
      【解决方案6】:

      我从不让 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 是一个普通对象(不是静态的),但每个应用程序只实例化一次。

      【讨论】:

      • @nawfal 业务应用层
      • 从未听说过这个词。用于“业务层”、“域”、“模型”等更多。我希望他们是一样的。
      • 别忘了使用,咳咳,USING 命令。您无需关闭与它的连接。
      【解决方案7】:

      让我们有一个愚蠢的基准,检查DataReader(.NET版本4)的速度是多少。 我从数据库中获取了一个记录(SQL Server 2000)并读取其所有字段。我重复了这个过程 1000 次。 DataReader拍摄了17.3327585秒,并且DataTable花了18.37320156,因此DataReader比LataTable快〜1.04秒,有1000个读取。

      所以,如果DataTable优先于DataTable首选,则可以获得0.00104秒的性能增益。

      也请看Is DataSet slower than DataReader due to…?

      【讨论】:

        【解决方案8】:

        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 和基准测试。

        【讨论】:

          【解决方案9】:

          这与我发布的 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,它会延迟返回数据,如枚举。如果您只需要枚举它,这对记忆很有帮助。要在内存中保存集合的副本,最好使用Read4Read5

          【讨论】:

            猜你喜欢
            • 2018-07-03
            • 1970-01-01
            • 2011-06-06
            • 1970-01-01
            • 2011-02-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-04-15
            相关资源
            最近更新 更多