web-dev-qa-db-ja.com

List <>をSQLストアドプロシージャに渡す

データベースの特定のレコードに複数のアイテムをロードする必要が頻繁にありました。例:Webページには、単一のレポートに含めるアイテムが表示されます。すべてのアイテムはデータベースのレコードです(レポートはレポートテーブルのレコード、アイテムはアイテムテーブルのレコードです)。ユーザーが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,'~')

関数が整数の1列のテーブルを返す場合。

私の質問はこれです:このようなものを処理するより良い方法はありますか?データベースの正規化などを求めているわけではないことに注意してください。私の質問は特にコードに関連しています。

50
Ryan Abbott

文字列結合ロジックはおそらく単純化できます。

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

これにより、文字列の連結が節約されます。これは、.Netでは高価です。

アイテムの保存方法に問題はないと思います。データベースへの旅行を制限しているのは良いことです。データ構造がintのリストよりも複雑な場合は、XMLをお勧めします。

注:これで文字列の連結が保存されるかどうかはコメントで尋ねられました(実際にはそうです)。それは素晴らしい質問だと思うので、フォローアップしたいと思います。

Reflector で開いた文字列を結合すると、Microsoftは、charポインターやUnSafeCharBufferと呼ばれる構造の使用を含む、いくつかの安全でない(Wordの意味での)テクニックを使用していることがわかります。実際にそれを煮詰めるとき、彼らがしていることは、ポインターを使用して空の文字列を渡り、結合を構築することです。 .Netで文字列の連結が非常に高価である主な理由は、文字列は不変であるため、連結ごとに新しい文字列オブジェクトがヒープに配置されることです。これらのメモリ操作は高価です。 String.Join(..)は、基本的にメモリを1回割り当ててから、ポインタでメモリを操作します。とても早い。

19
Jason Jackson

テクニックの潜在的な問題の1つは、非常に大きなリストを処理しないことです。データベースの最大文字列長を超える可能性があります。整数値を文字列の列挙に連結するヘルパーメソッドを使用します。各列挙は指定された最大値よりも小さくなります(以下の実装では、オプションで重複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();
    }
}
8
Joe

あなたはすでに持っていることを行い、区切られた文字列を渡してからテーブル値に解析するか、他の選択肢はXMLのウォッジを渡して、ほとんど同じです:

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

SQL 2008を見て、この種のことを処理する新しい機能が追加されているかどうかを確認する機会はまだありません。

5
Kev

テーブル値パラメーターを使用しないのはなぜですか? https://docs.Microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters

5
GaTechThomas

Sqlteam.comのテーブル値パラメーターの非常に明確な説明を次に示します。 テーブル値パラメーター

2
dotnetN00b

http://www.sommarskog.se/arrays-in-sql-2005.html を参照してください。この問題の詳細な説明と、使用できるさまざまなアプローチについて説明しています。

2
Phillip Wells

ストアドプロシージャの複数の値の単一フィールドのクエリ
http://www.norimek.com/blog/post/2008/04/Query-a-Single-Field-for-Multiple-Values-in-a-Stored-Procedure.aspx

1
Robert C. Barth