Передача списка <> в хранимую процедуру SQL

Мне часто приходилось загружать несколько элементов в определенную запись в базе данных. Например: на веб-странице отображаются элементы для включения в один отчет, все из которых являются записями в базе данных (отчет - это запись в таблице отчета, элементы - это записи в таблице элементов). Пользователь выбирает элементы для включения в один отчет через веб-приложение, и, допустим, он выбирает 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,'~')

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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
51
0
55 119
8

Ответы 8

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

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

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

Логику объединения строк, вероятно, можно упростить:

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

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

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

Примечание: В комментариях меня спросили, спасет ли это нас от конкатенации строк (это действительно так). Я считаю, что это отличный вопрос, и хотел бы продолжить его.

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

Это действительно спасет вас от конкатенации? Вы знаете, реализован ли метод с помощью StringBuilder?

Pablo Marambio 16.10.2008 22:33

Да, это избавит вас от конкатенации. String.Join использует UnsafeCharBuffer для выполнения concat (посмотрите на него в Reflector). Что касается LINQ, это просто перечисление по списку, вызывающее ToString (), что неизбежно, и создание массива. Создание массива может быть дорогостоящим.

Jason Jackson 16.10.2008 22:55

Я не знал, что у вас получится так, спасибо за подсказку!

Ryan Abbott 17.10.2008 19:01

И обратите внимание, что в .NET 4 вы можете отказаться от .ToArray().

Mark Hurd 19.02.2014 06:00

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

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();
    }
}

Хороший момент, я не думал о превышении длины струны. Хотя я сомневаюсь, что у меня будут случаи, которые хотя бы приблизились к этому, хорошо узнать это, спасибо!

Ryan Abbott 17.10.2008 19:02

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

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

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

Ознакомьтесь с дополнительными сведениями о TVP здесь и здесь или просто спросите у Google «возвращающие табличное значение параметры SQL Server 2008» - вы найдете много информации и примеров.

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

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

Поскольку для этого требуется тип таблицы, определяемый пользователем, который может оставаться в базе данных постоянно, если вы не гарантируете, что вы удаляете этот тип после каждого вызова.

alpav 08.11.2012 21:31

Им довольно сложно управлять, например, если вам нужно внести изменения в скрипт для динамического развертывания.

philw 05.08.2013 11:53

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

Другие вопросы по теме