我使用SP,这不是SP vs代码隐藏“构建您的SQL命令”问题。我正在寻找一种处理许多小事务的后端应用程序的高吞吐量方法。我使用SQLDataReader进行大多数返回,因为转发仅在大多数情况下适用于我。
我已经看到它做了很多种方式,并且我自己也使用了大部分方法。
定义和接受存储过程参数作为参数本身的方法,并使用cmd.Parameters.Add构建(使用或不指定DB值类型和/或长度)
将SP参数及其值组合到数组或哈希表中,然后传递给一个更抽象的方法来解析该集合,然后运行cmd.Parameters.Add
表示表的类,根据需要初始化类,设置表示字段的公共属性,以及调用Save,Load等方法
我确信还有其他我见过但现在也想不到的。我对所有建议持开放态度。
答案 0 :(得分:29)
此答案主要侧重于“选择”与更新/创建/删除操作。我认为一次更新多个或几个记录的情况比较少见,因此我认为“选择”是瓶颈往往发生的地方。也就是说,您需要了解您的应用程序(配置文件)。优化时间的最佳位置几乎总是在查询本身的数据库级别,而不是客户端代码。客户端代码只是管道:它不是您的应用程序的主要力量。但是,由于管道往往会在许多不同的应用程序中重复使用,我同样希望尽可能接近最佳状态,因此我对如何构建代码有很多话要说。
我的数据层中的选择查询/过程的通用方法如下所示:
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;
}
);
}
我要停在这里,所以我可以再次指出上面使用匿名方法创建参数的代码。
这是非常干净的代码,因为它将查询定义和参数创建放在同一个地方,同时仍然允许您将样板数据库连接/调用代码抽象到更可重用的地方。我不认为你的问题中的任何一个要点都涵盖了这种技术,而且碰巧也很快。我认为这涵盖了你问题的主旨。
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对象直接传递给表示层,并避免为简单地绑定到网格控件的记录创建不必要的对象。
更新/创建代码类似,区别在于您通常一次只更改一条记录而不是多条记录。
或者,我可以保存您阅读这篇长篇文章并告诉您使用Entity Framework;)
答案 1 :(得分:8)
就个人而言,我是代码生成的忠实粉丝。我推出自己的自制XML,在构建时我通过XSLT运行它来生成我的.CS文件。我在这篇文章Using XSLT to generate Performance Counters code中描述了这个过程。虽然该链接讨论了生成性能计数器代码,但我使用相同的过程来生成我的DAL。
所以我会创建一个像:
的XML<query name="LoadCustomerByName" returns="Customer">
<parameter name="name" type="String"/>
<text>SELECT ... FROM Customers WHERE name=@name</text>
</query>
然后XLST会将其转换为:
public Customer LoadCustomerByName(
SqlConnection conn,
SqlTransaction trn,
String name)
{
using (Sqlcommand cmd = new SqlCommand(@"SELECT ... FROM ...", conn, trn))
{
cmd.Parameters.AddWithValue("@name", name);
using (SqlDataReader rdr = cmd.ExecuteReader ())
{
Customer c = new Customer();
// Load c from rdr
return c;
}
}
}
现在我发布了很多关于XSLT转换实际做了什么的细节,但是真正重要的是这个方法让我可以绝对控制我如何创建DAL,并且它在每个方面都很灵活,因为生成了。 CS代码完全由我的XSLT驱动。我可以更改XLST,这将导致重新生成DAL中的每个方法。它可以轻松探索各种解决方案,它允许我在代码中添加工具(比如用于测量每个查询性能和使用频率的计数器)等等。
这基本上是各种VS设计师为您所做的,但如果您采取额外步骤来控制代码生成过程,您可以更灵活地控制结果。
答案 2 :(得分:3)
执行时间最快还是编程时间最快?您可以做的唯一增加#1吞吐量的方法是使用多个线程和连接来执行插入 - 您可以使用SQLCommand.BeginExecuteNonQuery
执行此操作答案 3 :(得分:2)
这个已经有一段时间了,但如果您愿意使用非常微型ORM,只需使用dapper-dot-net。
它具有非常干净的语法,速度极快,并且易于放入任何项目中。它正在StackOverflow的生产中使用,这个页面可能是由它带给你的。支持所有常用的SQL方法作为扩展方法,并支持所有内容的异步。
一些性能比较:
答案 4 :(得分:1)
这不是关于插入或更新,但我使用各种方法对读取速度进行了一些基准测试。 Of all, DataTable
routes seemed slower of the bunch.。乔尔的方法基本上是你能得到的最快的......
答案 5 :(得分:0)
我不喜欢的一件事是,收益回归大炮驻留在一个尝试...抓住块。因此,无法满足集中的异常处理/日志记录。
我使用了类似的appraoch,但是将IEnumerable作为参数传递。然后我不必使用收益率回报。