【问题标题】:Is it OK to pass DataReaders to constructors?可以将 DataReaders 传递给构造函数吗?
【发布时间】:2009-05-19 22:49:31
【问题描述】:

我正在维护一些 C# 2.0 代码,程序员使用一种模式,即通过打开 DataReader 然后将其传递给对象的构造函数来读取业务对象的集合。我看不出这有什么明显的问题,但对我来说感觉很糟糕。这样做可以吗?

private static void GetObjects()
{
    List<MyObject> objects = new List<MyObject>();
    string sql = "Select ...";
    SqlConnection connection = GetConnection();
    SqlCommand command = new SqlCommand(sql, connection);
    connection.Open();
    SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
    while (reader.Read())
        objects.Add(new MyObject(reader));
    reader.Close();
}

public MyObject(SqlDataReader reader)
{
    field0 = reader.GetString(0);
    field1 = reader.GetString(1);
    field2 = reader.GetString(2);
}

【问题讨论】:

  • 我的例子不清楚,但假设 MyObject 是一个单独的类。
  • 您可以更改您的代码以使其成为一个类... :)

标签: c# constructor sqldatareader


【解决方案1】:

通过将 DataReader 传递给对象的构造函数,您可以在业务对象和您选择的持久性技术之间建立非常紧密的耦合。

至少,这种紧密耦合会使重用和测试变得困难;在最坏的情况下,它可能会导致业务对象对数据库了解太多。

解决这个问题并不太难 - 您只需将对象初始化移出构造函数并移至不同的工厂类。

【讨论】:

  • 我认为在方法构造函数中将 SqlDataReader 更改为 IDataReader 对解决此问题也大有帮助。 IDataReader 足够通用,不会与任何底层持久性技术绑定。
  • 我不完全同意 - 虽然你获得了一些解耦,因为你不再依赖于特定的实现,传递 IDataReader 仍然束缚你使用原始 ADO.NET,像它一样生活在 System.Data 命名空间中,排除使用任何提供更高抽象的东西(NHibernate、Entity Framework、Lightspeed、Genome 等)。
  • IDataReader 相当复杂 - 使用它而不是 SqlDataReader 几乎没有任何收获。使用IDataReader 的唯一选择是可移植到不同的 ADO.NET 提供程序,这是您在到达它时最好通过的桥梁 - 该特定接口将是您最少的麻烦。
【解决方案2】:

传递一个读者让我畏缩,除非它是一种非公共的帮助方法来处理复制一行。不过绝对不是构造函数。

通过读取器将您的构造函数(您没有将 MyObject 放在类中,但您调用 new MyObject())连接到您的数据存储,我认为您的对象不是这样写的吗?

如果是我:

private static void GetObjects()
{
    List<MyObject> objects = new List<MyObject>();
    string sql = "Select ...";
    using (SqlConnection connection = GetConnection())
    {
        SqlCommand command = new SqlCommand(sql, connection);
        connection.Open();
        using(SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);)
        {
            while (reader.Read())
                objects.Add(_ReadRow(reader));
        }
    }
}

private static MyObject _ReadRow(SqlDataReader reader)
{
    MyObject o = new MyObject();
    o.field0 = reader.GetString(0);
    o.field1 = reader.GetString(1);
    o.field2 = reader.GetString(2);

    // Do other manipulation to object before returning

    return o;
}

class MyObject{}

【讨论】:

  • 这几乎肯定是我会做的——我相信有人会展示一种使用匿名方法或 lambdas 的方法。我很想知道这个解决方案有多“流行”。
  • "using(SqlDataReader reader..." 并不是真正需要的,对吧?由于连接已经结束,对它的处理将清理命令、游标等。
  • 如果类实现了 IDisposable,那么它希望你调用 Dispose。不要让它失望。
  • 然而,这个解决方案打破了 MyObject 的封装。这很糟糕,因为您确实在这里更改了有意义的保证: field0 等必须突然成为非私有的,即使您的助手是该类的成员,它们也不能(例如)是只读的。我更喜欢这种方法的原始代码方法 - 它仍然与 SqlDataReader 紧密耦合,并且更难阅读和启动。
  • Eamon,在对象上设置私有字段,目的是将数据转换为对象,这似乎适得其反。该对象的存在是为了存储您的数据并隐藏 SQL 层,因此它以您希望的方式存储 your 数据。我无法理解这一层应该向您隐藏数据的合理情况。换句话说,这个层的存在是为了隐藏持久性而不是隐藏你的数据。 (FWIW,您可以将这些字段传递给构造函数或使用反射。)
【解决方案3】:

我不会这样做,但我没有发现任何重大错误。

【讨论】:

    【解决方案4】:

    我会在 IDataReader 中传递实体,因为这有助于测试 MyObject。

    【讨论】:

    • 出于好奇,您是否尝试过这样做?我开始走这条路,虽然它看起来可能 IDataReader 是一个非常大的接口,带有像GetSchemaTable 这样的讨厌位。您的测试代码将很大、复杂且难以维护。而且您将要测试的大部分代码都将与实际的 sql 数据库紧密相关——几乎所有的错误都存在于这种耦合中(事务、查询问题、时间等)。我认为无论如何都不值得。
    【解决方案5】:

    我称之为“泄漏抽象”。

    我喜欢在尽可能窄的范围内使用持久性对象:获取它们、使用它们、清理它们。如果有一个定义明确的持久化对象,你可以要求它把查询结果映射到一个对象或集合中,在方法范围内关闭阅读器,然后将对象或集合返回给你的客户端。

    【讨论】:

      【解决方案6】:

      (编辑:此答案仅关注“相对较低级别的含义是什么”而不是整体设计含义。看起来其他答案已经涵盖了这些,所以我不会发表评论:)

      嗯,您给出的代码肯定有问题,因为没有任何东西可以关闭连接、命令或阅读器。具体来说,您的连接分配行通常应如下所示:

      using (SqlConnection connection = GetConnection())
      {
          ...
      }
      

      您可能会认为这只是吹毛求疵,它只是示例代码,清理并不重要 - 但需要清理的资源的“所有权”正是问题所在将DataReader 传递给构造函数。

      我认为只要你事后记录谁“拥有”读者就可以了。例如,在Image.FromStream 中,图像随后拥有流,并且可能不会对您自己关闭它(取决于图像格式和其他一些事情)。其他时候,关闭仍然是您的责任。这必须非常仔细地记录下来,如果具有构造函数的类型获得所有权,它应该实现IDisposable 以使清理更容易并且更明显地表明需要清理。 p>

      在您的情况下,构造函数似乎没有拥有读者的所有权,这是一个非常好的(和更简单)的替代方案。只需记录一下,调用者仍然需要适当地关闭阅读器。

      【讨论】:

      • 我很好 - 我添加了一个“通常”,因为在极少数情况下它最终的工作方式略有不同,但通常使用 using 语句是要走的路。
      【解决方案7】:

      我完全同意 duffymo (+1)(和 Bevan)

      我最近一直在思考的一个想法是,如果我必须有一个依赖项,当然当我将一个读取器映射到一个对象时,我必须有一个依赖项,也许我应该让它完全明确实际上写了一个扩展方法来实现它,比如......

      //This Static extension class in the same namespace (but not necesarrily assembly) as my BO
      
      public static class DataReaderExtensions
      {
        public static List<MyObject> GetMyObjects(this DataReader reader)
        {
      
        }
      }
      

      这样,只要我在我的业务对象的引用范围内,我的数据读取器现在就会有一个适合我需要的方法......这个想法可能需要更加充实,但我认为这将是优雅。

      【讨论】:

        【解决方案8】:

        要分离业务层和数据层,请使用 YIELD

          public IEnumerable<IDataReader> getIDataReader(string sql)
        {
        
            using (SqlConnection conn = new SqlConnection("cadena de conexion"))
            {
                using (SqlCommand da = new SqlCommand(sql, conn))
                {
                    conn.Open();
                    using (SqlDataReader dr = da.ExecuteReader)
                    {
                        if (dr.HasRows)
                        {
                            while (dr.Read)
                            {
                                yield return dr;
                            }
                        }
                    }
                    conn.Close();
                }
            }
        }
        

        【讨论】:

          【解决方案9】:

          将数据读取器传递给构造函数可能值得商榷,但据我所知,这是一种众所周知的方法。但是无论谁使用它,都应该传递一个抽象,即不是 SqlDataReader 而是 IDataReader。然而 IDataReader 并不是最优的;它应该是 IDataRecord,它包含相应记录的结果,而不是允许迭代!!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-04-27
            • 1970-01-01
            • 2012-12-25
            • 2010-10-21
            • 1970-01-01
            相关资源
            最近更新 更多