Передача списка <> в хранимую процедуру SQL - PullRequest
50 голосов
/ 16 октября 2008

Мне часто приходилось загружать несколько элементов в конкретную запись в базе данных. Например: веб-страница отображает элементы для включения в один отчет, все из которых являются записями в базе данных (отчет - это запись в таблице отчета, элементы - это записи в таблице элементов). Пользователь выбирает элементы для включения в один отчет через веб-приложение, и, скажем, он выбирает 3 элемента и отправляет. Процесс добавит эти 3 элемента в этот отчет, добавив записи в таблицу с именем ReportItems (ReportId, ItemId).

В настоящее время я хотел бы сделать что-то подобное в коде:

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,'~')

Где функция возвращает целую таблицу из одного столбца.

У меня такой вопрос: есть ли лучший способ справиться с чем-то подобным? Обратите внимание, я не спрашиваю о нормализации базы данных или о чем-либо подобном, мой вопрос связан именно с кодом.

Ответы [ 8 ]

34 голосов
/ 21 января 2009

Если вам нужен вариант с SQL Server 2008, есть новая функция под названием «Табличные параметры», чтобы решить эту проблему.

Ознакомьтесь с более подробной информацией о TVP здесь и здесь или просто спросите у Google "Табличные параметры SQL Server 2008" - вы найдете множество информации и примеров.

Настоятельно рекомендуется - если , вы можете перейти на SQL Server 2008 ...

19 голосов
/ 16 октября 2008

Ваша логика объединения строк, вероятно, может быть упрощена:

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

Это сэкономит вам некоторую конкатенацию строк, которая в .Net дорогая.

Я не думаю, что что-то не так с тем, как вы сохраняете вещи. Вы ограничиваете поездки в БД, что хорошо. Если бы ваша структура данных была более сложной, чем список целых, я бы предложил XML.

Примечание: Меня спросили в комментариях, спасет ли нас какое-либо объединение строк (оно не зависит). Я думаю, что это отличный вопрос, и я хотел бы ответить на него.

Если вы очистите открытую строку. Присоединитесь к Reflector , и вы увидите, что Microsoft использует несколько небезопасных (в смысле .Net в смысле этого слова) методов, включая использование указателя на символ и структуру с именем UnSafeCharBuffer. То, что они делают, когда вы действительно сводите это, используют указатели, чтобы пройтись по пустой строке и создать соединение. Помните, что основная причина конкатенации строк в .Net заключается в том, что для каждой конкатенации в кучу помещается новый строковый объект, поскольку строка неизменна. Эти операции с памятью дороги. String.Join (..), по сути, выделяет память один раз, а затем обрабатывает ее указателем. Очень быстро.

8 голосов
/ 16 октября 2008

Одна потенциальная проблема с вашей техникой заключается в том, что она не обрабатывает очень большие списки - вы можете превысить максимальную длину строки для вашей базы данных. Я использую вспомогательный метод, который объединяет целочисленные значения в перечисление строк, каждая из которых меньше указанного максимума (следующая реализация также необязательно проверяет и удаляет дубликаты идентификаторов):

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();
    }
}
5 голосов
/ 12 июня 2010

Почему бы не использовать табличный параметр? https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters

5 голосов
/ 16 октября 2008

Вы либо делаете то, что уже получили, передаете строку с разделителями, а затем анализируете значение таблицы, либо другой вариант заключается в передаче XML-кода и в некотором роде:

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

У меня еще не было возможности взглянуть на SQL 2008, чтобы узнать, добавили ли они какие-либо новые функциональные возможности для обработки подобных вещей.

2 голосов
/ 28 сентября 2010

Вот очень четкое объяснение параметров с табличными значениями из sqlteam.com: Параметры с табличными значениями

2 голосов
/ 16 октября 2008

См. http://www.sommarskog.se/arrays-in-sql-2005.html для подробного обсуждения этой проблемы и различных подходов, которые вы могли бы использовать.

1 голос
/ 17 октября 2008

Запрос одного поля для нескольких значений в хранимой процедуре
http://www.norimek.com/blog/post/2008/04/Query-a-Single-Field-for-Multiple-Values-in-a-Stored-Procedure.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...