51

我经常不得不将多个项目加载到数据库中的特定记录中。例如:网页显示单个报表要包含的项目,所有这些项目都是数据库中的记录(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,'~')

函数返回一列整数表。

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

4

8 回答 8

35

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

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

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

于 2009-01-21T20:48:47.787 回答
19

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

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

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

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

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

If you peel open string.Join with Reflector you will see that Microsoft is using a couple of unsafe (in the .Net sense of the word) techniques, including using a char pointer and a structure called UnSafeCharBuffer. What they are doing, when you really boil it down, is using pointers to walk across an empty string and build up the join. Remember that the main reason string concatenation is so expensive in .Net is that a new string object is placed on the heap for every concatenation, because string is immutable. Those memory operations are expensive. String.Join(..) is essentially allocating the memory once, then operating upon it with a pointer. Very fast.

于 2008-10-16T18:28:31.333 回答
8

您的技术的一个潜在问题是它不能处理非常大的列表 - 您可能会超过数据库的最大字符串长度。我使用一个辅助方法将整数值连接成一个字符串枚举,每个字符串都小于指定的最大值(以下实现还可以选择检查并删除重复的 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();
    }
}
于 2008-10-16T19:21:55.687 回答
5

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

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

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

于 2008-10-16T18:25:54.940 回答
5

为什么不使用表值参数? https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters

于 2010-06-11T20:06:40.383 回答
2

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

于 2008-10-16T19:26:59.810 回答
2

这是 sqlteam.com 对表值参数的一个非常清晰的解释:表值参数

于 2010-09-28T14:24:38.240 回答
1

在存储过程中查询单个字段的多个值
http://www.norimek.com/blog/post/2008/04/Query-a-Single-Field-for-Multiple-Values-in-a-Stored-Procedure .aspx

于 2008-10-16T23:04:10.357 回答