簡體   English   中英

將List <>傳遞給SQL存儲過程

[英]Passing List<> to SQL Stored Procedure

我經常不得不將多個項目加載到數據庫中的特定記錄。 例如:網頁顯示要包含在單個報告中的項目,所有項目都是數據庫中的記錄(“報告”是“報告”表中的記錄,“項目”是“項目”表中的記錄)。 用戶正在通過網絡應用程序選擇要包含在單個報告中的項目,假設他們選擇了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,'~')

該函數返回一列整數表。

我的問題是:有沒有更好的方法來處理這樣的事情? 注意,我不是在問數據庫規范化之類的問題,我的問題專門與代碼有關。

如果您選擇使用SQL Server 2008,則有一個名為“表值參數”的新功能可以解決此確切問題。

此處此處查看有關TVP的更多詳細信息,或者只是向Google詢問“ SQL Server 2008表值參數”-您會發現大量信息和示例。

強烈建議- 如果可以遷移到SQL Server 2008 ...

您的字符串連接邏輯可能可以簡化:

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

這樣可以節省一些字符串連接,在.Net中這是昂貴的。

我認為您保存項目的方式沒有任何問題。 您限制了對數據庫的訪問,這是一件好事。 如果您的數據結構比整數列表更復雜,我建議使用XML。

注意:我在評論中被問到這是否可以節省任何字符串連接(的確如此)。 我認為這是一個很好的問題,並希望對此進行跟進。

如果剝開字符串。與Reflector結合使用,您會看到Microsoft正在使用幾種不安全的技術(按.Net的說法),包括使用char指針和稱為UnSafeCharBuffer的結構。 當您真正將其歸結時,他們正在做的事情是使用指針遍歷一個空字符串並建立連接。 請記住,.Net中字符串連接如此昂貴的主要原因是,每個字符串都會在堆上放置一個新的字符串對象,因為字符串是不可變的。 這些存儲器操作是昂貴的。 String.Join(..)本質上是分配一次內存,然后使用指針對其進行操作。 非常快。

該技術的一個潛在問題是它不能處理非常大的列表-您可能超過了數據庫的最大字符串長度。 我使用了一個輔助方法,該方法將整數值連接到一個字符串枚舉中,每個字符串小於指定的最大值(以下實現也可以選擇檢查並刪除重復的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();
    }
}

您可以執行已有的操作,傳入一個定界的字符串,然后解析為表值,或者另一種選擇是傳入大量的XML,大致相同:

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

我還沒有機會看一下SQL 2008,看看他們是否添加了任何新功能來處理這種事情。

有關此問題以及可以使用的不同方法的詳細討論,請參見http://www.sommarskog.se/arrays-in-sql-2005.html

這是來自sqlteam.com的表值參數的非常清晰的解釋: 表值參數

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM