我正在维护一些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);
}
答案 0 :(得分:5)
通过将DataReader传递给对象的构造函数,您可以在业务对象和您选择的持久性技术之间建立非常紧密的耦合。
至少,这种紧密耦合将使重用和测试变得困难;在最坏的情况下,它可能导致业务对象对数据库了解太多。
解决这个问题并不太困难 - 您只需将对象初始化移出构造函数并进入不同的工厂类。
答案 1 :(得分: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{}
答案 2 :(得分:1)
我不会这样做,但我没有看到任何重大错误。
答案 3 :(得分:1)
(编辑:这个答案仅关注“相对较低级别的影响是什么”,而不是整体设计含义。看起来其他答案已经涵盖了那些,所以我不会评论:)) p>
嗯,你给出的代码肯定有问题,因为没有关闭连接,命令或阅读器。具体来说,您的连接分配行通常应如下所示:
using (SqlConnection connection = GetConnection())
{
...
}
您可能认为这只是挑剔,而且它只是示例代码而清理并不重要 - 但需要清理资源的“所有权”正是问题所在将DataReader
传递给构造函数。
我认为只要您记录之后“拥有”读者的人就可以了。例如,在Image.FromStream
中,图像之后拥有该流,并且可能不会亲自关闭它(取决于图像格式和其他一些东西)。其他时候,你仍有责任关闭。必须非常仔细地记录这一点,并且如果具有构造函数的类型具有所有权,则应该实现IDisposable
以使清理更容易和以使清除更加明显。
在您的情况下,看起来构造函数不取得读者的所有权,这是一个非常好(和更简单)的替代方案。只需记录下来,调用者仍然需要适当地关闭阅读器。
答案 4 :(得分:1)
我会在IDataReader中传递实体,因为这有助于测试MyObject。
答案 5 :(得分:1)
我称之为“漏洞抽象”。
我喜欢在尽可能最窄的范围内使用持久性对象:获取它们,使用它们,清理它们。如果存在定义明确的持久性对象,则可以要求它将查询结果映射到对象或集合中,在方法范围内关闭阅读器,并将对象或集合返回给客户端。
答案 6 :(得分:0)
我完全同意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)
{
}
}
这样,只要我在业务对象的参考范围内,我的datareader现在将有一个根据我的需求量身定制的方法......这个想法可能需要更多地充实,但我认为这将是优雅。
答案 7 :(得分: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();
}
}
}
答案 8 :(得分:0)
将数据读取器传递给构造函数可能存在争议,但据我所知 - 这是一种众所周知的方法。但无论谁使用它,都应该传递一个抽象,即不是SqlDataReader而是IDataReader。然而,IDataReader不是最佳的;它应该是IDataRecord,它包含相应记录的结果,而不是允许迭代!!