SQL Server最快的插入,更新和选择方法

Ian*_*Ian 10 c# sql-server asp.net ado.net

我使用SP,这不是SP vs代码隐藏"构建您的SQL命令"问题.我正在寻找一种处理许多小事务的后端应用程序的高吞吐量方法.我使用SQLDataReader进行大部分返回,因为转发仅适用于大多数情况.

我已经看到它做了很多种方式,并且我自己也使用了大部分方法.

  1. 定义和接受存储过程参数作为参数本身的方法,并使用cmd.Parameters.Add构建(使用或不指定DB值类型和/或长度)

  2. 将SP参数及其值组装到数组或散列表中,然后传递给一个更抽象的方法来解析集合,然后运行cmd.Parameters.Add

  3. 表示表的类,根据需要初始化类,设置表示表字段的公共属性,以及调用Save,Load等方法

我确信还有其他我见过但现在也想不到的.我对所有建议持开放态度.

Joe*_*orn 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这让我可以编写使用匿名方法添加参数的公共数据层方法.显示的代码适用于.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;
       }
     );
}
Run Code Online (Sandbox Code Playgroud)

我要停在这里,所以我可以再次指向上面的代码,它使用匿名方法创建参数.

这是非常干净的代码,因为它将查询定义和参数创建放在同一个地方,同时仍然允许您将样板数据库连接/调用代码抽象到更可重用的地方.我不认为你的问题中的任何一个要点都涵盖了这种技术,而且碰巧也很快.我认为这涉及到你的问题的主旨.


不过,我想继续解释这一切是如何融合在一起的.其余的相当简单,但也很容易把它扔到列表或类似的东西,并弄错了,最终伤害性能.继续前进,业务层然后使用工厂将查询结果转换为对象(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]
            //...
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以将它们全部组合成一个专门用于保存工厂方法的静态类,而不是让它们存在于类中.

我需要对原始检索方法进行一次更改.该方法反复"产生"相同的对象,并且这并不总是那么好.我们想要做的不同之处就是强制使用当前记录所代表的对象的副本,这样当读者为下一条记录变异时,我们就会使用干净的数据.我一直等到显示工厂方法后才能在最终代码中使用它.新的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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们将这样称为新的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;
       }
     );
}
Run Code Online (Sandbox Code Playgroud)

显然,最后一种方法可以扩展为包含所需的任何其他业务逻辑.事实证明这个代码非常快,因为它利用了IEnumerable的惰性评估功能.缺点是它往往会创建大量短期对象,这可能会损害您询问的事务性能.为了解决这个问题,我有时会破坏良好的n层并将IDataRecord对象直接传递给表示层,并避免为简单地绑定到网格控件的记录创建不必要的对象.

更新/创建代码是类似的,区别在于您通常一次只更改一条记录而不是多条记录.

或者,我可以保存您阅读这篇长篇文章并告诉您使用实体框架;)

  • *"或者,我可以保存你阅读这篇长篇文章并告诉你使用Entity Framework;)"* - OP说他想要*fast*方法.对于除了最微不足道的任务之外的任何事情,EF都远远不够快 (3认同)

Rem*_*anu 9

就个人而言,我是代码生成的忠实粉丝.我推出自己的自制XML,在构建时我通过XSLT运行它来生成我的.CS文件.我在这篇文章中描述了使用XSLT生成性能计数器代码的过程.虽然该链接讨论了生成性能计数器代码,但我使用相同的过程来生成我的DAL.

所以我会创建一个XML:

<query name="LoadCustomerByName" returns="Customer">
  <parameter name="name" type="String"/>
  <text>SELECT ... FROM Customers WHERE name=@name</text>
</query>
Run Code Online (Sandbox Code Playgroud)

然后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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我详细介绍了XSLT转换实际上做了什么,但真正重要的是这个方法让我可以绝对控制我如何创建DAL,并且它在各个方面都很灵活,因为生成的.CS代码是完全由我的XSLT驱动.我可以更改XLST,这将导致重新生成DAL中的每个方法.它可以轻松探索各种解决方案,它允许我为代码添加工具(如计数器来测量每个查询性能和使用频率)等等.

这基本上是各种VS设计师为您所做的,但如果您采取额外步骤来控制代码生成过程,您可以更灵活地控制结果.