5

我正在维护一些 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);
}
4

9 回答 9

5

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

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

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

于 2009-05-19T22:55:57.563 回答
3

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

传递一个阅读器将您的构造函数(您没有将 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{}
于 2009-05-19T22:53:33.670 回答
1

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

于 2009-05-19T22:51:45.047 回答
1

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

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

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

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

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

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

于 2009-05-19T22:53:57.537 回答
1

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

于 2009-05-19T22:54:28.347 回答
1

我称之为“泄漏抽象”。

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

于 2009-05-19T23:01:35.213 回答
0

我完全同意达菲莫(+1)(和贝文)

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

//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)
  {

  }
}

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

于 2009-05-19T23:10:35.710 回答
0

要分离业务层和数据层,请使用 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();
        }
    }
}
于 2014-06-23T14:20:39.703 回答
0

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

于 2014-11-11T16:07:56.503 回答