【问题标题】:Passing List<> to SQL Stored Procedure将 List<> 传递给 SQL 存储过程
【发布时间】:2010-09-17 14:30:06
【问题描述】:

我经常不得不将多个项目加载到数据库中的特定记录。例如:网页显示单个报表要包含的项目,所有这些项目都是数据库中的记录(Report 是 Report 表中的记录,Items 是 Item 表中的记录)。用户正在通过 Web 应用程序选择要包含在单个报告中的项目,假设他们选择了 3 个项目并提交。该过程将通过将记录添加到名为 ReportItems (ReportId,ItemId) 的表中来将这 3 个项目添加到此报告中。

目前,我会在代码中做这样的事情:

public void AddItemsToReport(string connStr, int Id, List<int> itemList)
{
    Database db = DatabaseFactory.CreateDatabase(connStr);

    string sqlCommand = "AddItemsToReport"
    DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand);

    string items = "";
    foreach (int i in itemList)
        items += string.Format("{0}~", i);

    if (items.Length > 0)
        items = items.Substring(0, items.Length - 1);

    // Add parameters
    db.AddInParameter(dbCommand, "ReportId", DbType.Int32, Id);
    db.AddInParameter(dbCommand, "Items", DbType.String, perms);
    db.ExecuteNonQuery(dbCommand);
}

这在存储过程中:

INSERT INTO ReportItem (ReportId,ItemId)
SELECT  @ReportId,
          Id
FROM     fn_GetIntTableFromList(@Items,'~')

函数返回一列整数表。

我的问题是:有没有更好的方法来处理这样的事情?请注意,我不是在问数据库规范化或类似的问题,我的问题与代码特别相关。

【问题讨论】:

    标签: c# sql sql-server tsql stored-procedures


    【解决方案1】:

    如果您可以选择使用 SQL Server 2008,那么有一个名为“表值参数”的新功能可以解决这个确切的问题。

    查看有关 TVP herehere 的更多详细信息,或者只是向 Google 询问“SQL Server 2008 表值参数” - 您会找到大量信息和示例。

    强烈推荐 - 如果您可以迁移到 SQL Server 2008...

    【讨论】:

      【解决方案2】:

      您的字符串连接逻辑可能可以简化:

      string items = 
          string.Join("~", itemList.Select(item=>item.ToString()).ToArray());
      

      这将为您节省一些字符串连接,这在 .Net 中很昂贵。

      我认为您保存项目的方式没有任何问题。您正在限制对数据库的访问,这是一件好事。如果您的数据结构比整数列表更复杂,我建议使用 XML。

      注意:我在 cmets 中被问到这是否会为我们节省任何字符串连接(确实如此)。我认为这是一个很好的问题,并愿意跟进。

      如果您使用Reflector 剥离打开的string.Join,您将看到Microsoft 使用了一些不安全的(在.Net 的意义上)技术,包括使用char 指针和称为UnSafeCharBuffer 的结构。当你真正归结为它时,他们正在做的是使用指针遍历一个空字符串并建立连接。请记住,.Net 中字符串连接如此昂贵的主要原因是每次连接都会在堆上放置一个新的字符串对象,因为字符串是不可变的。这些内存操作很昂贵。 String.Join(..) 本质上是分配一次内存,然后用指针对其进行操作。非常快。

      【讨论】:

      • 这真的能让你免于串联吗?你知道这个方法是不是用StringBuilder实现的吗?
      • 是的,它将使您免于串联。 String.Join 使用 UnsafeCharBuffer 进行连接(在 Reflector 中查看)。就 LINQ 而言,这只是在列表中枚举调用 ToString(),这是不可避免的,并创建一个数组。数组创建可能很昂贵。
      • 我不知道你可以那样做,谢谢你的提示!
      • 请注意,在 .NET 4 中,您可以删除 .ToArray()
      【解决方案3】:

      您的技术的一个潜在问题是它不能处理非常大的列表 - 您可能会超过数据库的最大字符串长度。我使用了一个辅助方法,它将整数值连接成一个字符串枚举,每个字符串都小于指定的最大值(以下实现还可以选择检查并删除重复的 id):

      public static IEnumerable<string> ConcatenateValues(IEnumerable<int> values, string separator, int maxLength, bool skipDuplicates)
      {
          IDictionary<int, string> valueDictionary = null;
          StringBuilder sb = new StringBuilder();
          if (skipDuplicates)
          {
              valueDictionary = new Dictionary<int, string>();
          }
          foreach (int value in values)
          {
              if (skipDuplicates)
              {
                  if (valueDictionary.ContainsKey(value)) continue;
                  valueDictionary.Add(value, "");
              }
              string s = value.ToString(CultureInfo.InvariantCulture);
              if ((sb.Length + separator.Length + s.Length) > maxLength)
              {
                  // Max length reached, yield the result and start again
                  if (sb.Length > 0) yield return sb.ToString();
                  sb.Length = 0;
              }
              if (sb.Length > 0) sb.Append(separator);
              sb.Append(s);
          }
          // Yield whatever's left over
          if (sb.Length > 0) yield return sb.ToString();
      }
      

      然后你使用它:

      using(SqlCommand command = ...)
      {
          command.Connection = ...;
          command.Transaction = ...; // if in a transaction
          SqlParameter parameter = command.Parameters.Add("@Items", ...);
          foreach(string itemList in ConcatenateValues(values, "~", 8000, false))
          {
              parameter.Value = itemList;
              command.ExecuteNonQuery();
          }
      }
      

      【讨论】:

      • 好点,我没想过超过字符串长度。虽然我怀疑我会遇到甚至接近的案例,但学习这一点很好,谢谢!
      【解决方案4】:

      你要么做你已经得到的,传入一个分隔的字符串,然后解析为一个表值,或者另一个选择是传入一个 XML 和有点相同:

      http://weblogs.asp.net/jgalloway/archive/2007/02/16/passing-lists-to-sql-server-2005-with-xml-parameters.aspx

      我还没有机会查看 SQL 2008,看看他们是否添加了任何新功能来处理这类事情。

      【讨论】:

        【解决方案5】:

        【讨论】:

        • 因为它需要用户定义的表类型,除非您保证在每次调用后删除此类型,否则它可能会永久保留在数据库中。
        • 它们有点难以管理,例如,如果您必须对实时部署进行脚本更改。
        【解决方案6】:

        请参阅http://www.sommarskog.se/arrays-in-sql-2005.html,详细讨论此问题以及您可以使用的不同方法。

        【讨论】:

          【解决方案7】:

          下面是来自 sqlteam.com 的对表值参数的非常清晰的解释:Table Valued Parameters

          【讨论】:

            【解决方案8】:
            猜你喜欢
            • 1970-01-01
            • 2016-06-09
            • 2011-10-23
            • 2015-08-20
            • 2014-08-29
            • 1970-01-01
            • 1970-01-01
            • 2014-09-29
            • 2013-11-26
            相关资源
            最近更新 更多