此答案主要关注“选择”与更新/创建/删除操作。我认为一次更新超过一条或几条记录的情况很少见,因此我也认为“选择”是容易发生瓶颈的地方。也就是说,您需要了解您的应用程序(配置文件)。集中优化时间的最佳位置几乎总是在查询本身的数据库级别,而不是客户端代码。客户端代码只是管道:它不是您的应用程序的主要力量。然而,由于管道倾向于在许多不同的应用程序中重复使用,我确实很同情尽可能接近最佳状态的愿望,因此对于如何构建该代码,我确实有很多话要说。
我的数据层中有一个用于选择查询/过程的通用方法,如下所示:
private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
//ConnectionString is a private static property in the data layer
// You can implement it to read from a config file or elsewhere
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
rdr.Close();
}
}
}
这让我可以编写使用匿名方法添加参数的公共数据层方法。显示的代码适用于 .Net 2.0+,但使用 .Net 3.5 可以写得更短:
public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead of a full sql query
return Retrieve(
@"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}
我将在此停止,以便再次向您指出上面使用匿名方法创建参数的代码。
这是非常干净的代码,因为它将查询定义和参数创建放在同一个地方,同时仍然允许您将样板数据库连接/调用代码抽象到更可重用的地方。我认为您问题中的任何要点都没有涵盖这种技术,而且它的速度也非常快。我认为这涵盖了您问题的主旨。
不过,我想继续解释这一切是如何结合在一起的。其余部分相当简单,但也很容易将其扔到列表或类似列表中并出错,最终损害性能。继续前进,业务层然后使用工厂将查询结果转换为对象(c# 3.0 或更高版本):
public class Foo
{
//various normal properties and methods go here
public static Foo FooFactory(IDataRecord record)
{
return new Foo
{
Property1 = record[0],
Property2 = record[1]
//...
};
}
}
除了将它们放在它们的类中之外,您还可以将它们全部组合到一个静态类中,专门用于保存工厂方法。
我需要对原始检索方法进行一项更改。该方法一遍又一遍地“产生”相同的对象,但这并不总是那么好。为了让它工作,我们想要做不同的事情是强制复制由当前记录表示的对象,这样当阅读器为下一条记录发生变异时,我们正在使用干净的数据。我等到展示了工厂方法之后,才能在最终代码中使用它。新的 Retrieve 方法如下所示:
private static IEnumerable<T> Retrieve(Func<IDataRecord, T> factory,
string sql, Action<SqlParameterCollection> addParameters)
{
//ConnectionString is a private static property in the data layer
// You can implement it to read from a config file or elsewhere
using (var cn = new SqlConnection(ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return factory(rdr);
rdr.Close();
}
}
}
现在我们可以像这样调用这个新的 Retrieve() 方法:
public IEnumerable<Foo> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead of a full sql query
return Retrieve(Foo.FooFactory,
@"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}
显然,最后一种方法可以扩展为包含所需的任何其他业务逻辑。事实证明,这段代码非常快,因为它利用了 IEnumerable 的惰性求值特性。缺点是它往往会创建许多短期对象,这可能会损害您询问的事务性能。为了解决这个问题,我有时会打破良好的 n 层并将 IDataRecord 对象直接传递到表示层,并避免为立即绑定到网格控件的记录创建不必要的对象。
更新/创建代码类似,不同之处在于您通常一次只更改一条记录,而不是多条记录。
或者,我可以省去你阅读这篇长篇文章,告诉你使用实体框架;)