C # SQL Server - передача списка хранимой процедуре

112

Я вызываю хранимую процедуру SQL Server из моего кода C #:

using (SqlConnection conn = new SqlConnection(connstring))
{
   conn.Open();
   using (SqlCommand cmd = new SqlCommand("InsertQuerySPROC", conn))
   {
      cmd.CommandType = CommandType.StoredProcedure;

      var STableParameter = cmd.Parameters.AddWithValue("@QueryTable", QueryTable);
      var NDistanceParameter = cmd.Parameters.AddWithValue("@NDistanceThreshold", NDistanceThreshold);
      var RDistanceParameter = cmd.Parameters.AddWithValue(@"RDistanceThreshold", RDistanceThreshold);

      STableParameter .SqlDbType = SqlDbType.Structured;
      NDistanceParameter.SqlDbType = SqlDbType.Int;
      RDistanceParameter.SqlDbType = SqlDbType.Int;

      // Execute the query
      SqlDataReader QueryReader = cmd.ExecuteReader();

Моя сохраненная процедура довольно стандартна, но соединяется с QueryTable (отсюда необходимость использования хранимой процедуры).

Теперь: я хочу добавить список строк List<string>к набору параметров. Например, мой сохраненный запрос proc выглядит так:

SELECT feature 
FROM table1 t1 
INNER JOIN @QueryTable t2 ON t1.fid = t2.fid 
WHERE title IN <LIST_OF_STRINGS_GOES_HERE>

Однако список строк динамический и насчитывает несколько сотен.

Есть ли способ передать список строк List<string>в хранимую процедуру ??? Или есть способ лучше?

Большое спасибо, Бретт

Бретт
источник
Возможно, дубликат stackoverflow.com/questions/209686/…
Андрей Агибалов
Какая версия SQL Server ?? 2005 ?? 2008 ?? 2008 R2 ?? SQL Server 2008 и новее имеет концепцию «возвращающих табличное значение параметров» (подробности см. В ответе Redth)
marc_s
Несвязанный совет: SqlDataReader также является IDisposable, поэтому должен быть в usingблоке.
Ричардиссимо

Ответы:

179

Если вы используете SQL Server 2008, есть новая функция, называемая типом таблицы, определяемой пользователем. Вот пример того, как его использовать:

Создайте свой определяемый пользователем тип таблицы:

CREATE TYPE [dbo].[StringList] AS TABLE(
    [Item] [NVARCHAR](MAX) NULL
);

Затем вам нужно правильно использовать его в вашей хранимой процедуре:

CREATE PROCEDURE [dbo].[sp_UseStringList]
    @list StringList READONLY
AS
BEGIN
    -- Just return the items we passed in
    SELECT l.Item FROM @list l;
END

Наконец, вот несколько sql для использования в C #:

using (var con = new SqlConnection(connstring))
{
    con.Open();

    using (SqlCommand cmd = new SqlCommand("exec sp_UseStringList @list", con))
    {
        using (var table = new DataTable()) {
          table.Columns.Add("Item", typeof(string));

          for (int i = 0; i < 10; i++)
            table.Rows.Add("Item " + i.ToString());

          var pList = new SqlParameter("@list", SqlDbType.Structured);
          pList.TypeName = "dbo.StringList";
          pList.Value = table;

          cmd.Parameters.Add(pList);

          using (var dr = cmd.ExecuteReader())
          {
            while (dr.Read())
                Console.WriteLine(dr["Item"].ToString());
          }
         }
    }
}

Чтобы выполнить это из SSMS

DECLARE @list AS StringList

INSERT INTO @list VALUES ('Apple')
INSERT INTO @list VALUES ('Banana')
INSERT INTO @list VALUES ('Orange')

-- Alternatively, you can populate @list with an INSERT-SELECT
INSERT INTO @list
   SELECT Name FROM Fruits

EXEC sp_UseStringList @list
Redth
источник
10
Должен ли он определять datatable для установки значения параметра? Любой другой световой подход?
ca9163d9
2
Мы попробовали это, но обнаружили, что его недостаток не поддерживается фреймворком Enitiy
Бишой Ханна
7
БУДЬТЕ ОСТОРОЖНЫ С ДАННЫМ РЕШЕНИЕМ, ЕСЛИ ВЫ ИСПОЛЬЗУЕТЕ LINQ2SQL, поскольку он НЕ ПОДДЕРЖИВАЕТ ОПРЕДЕЛЕННЫЕ ПОЛЬЗОВАТЕЛЕМ ТАБЛИЦЫ В КАЧЕСТВЕ ПАРАМЕТРОВ !!! Обходной путь можно найти в ответе Джона Рейнора, используя списки, разделенные запятыми, и функцию синтаксического анализатора, однако у этого также есть недостатки ....
Fazi
3
@Fazi в этом случае не используйте Linq2SQL. Предпочтительнее конкатенация и синтаксический анализ строк в T-SQL
Панайотис Канавос
2
Да, так как же вы на самом деле выполняете это из SSMS?
Sinaesthetic
21

Типичный шаблон в этой ситуации - передать элементы в списке, разделенном запятыми, а затем в SQL разделить его в таблицу, которую вы можете использовать. Большинство людей обычно создают для этого определенную функцию, например:

 INSERT INTO <SomeTempTable>
 SELECT item FROM dbo.SplitCommaString(@myParameter)

А затем вы можете использовать его в других запросах.

Tejs
источник
17
позвольте мне добавить ссылку на реализацию dbo.SplitCommaString для полноты: goo.gl/P9ROs
Вели Гебрев
3
А что будет, если в одном из ваших полей данных стоит запятая?
Кевин Панко,
3
Вместо этого разделите его трубами.
Alex In Paris
@AlexInParis Что делать, если в одном из ваших полей данных есть канал?
RayLoveless
2
Затем используйте то, чего нет в ваших полях данных. При необходимости очистите свои данные, но не так много данных, которые я когда-либо видел, когда-либо использовали трубы. Если они абсолютно необходимы, найдите другой символ, например ¤ или §.
Alex In Paris,
9

Нет, массивы / списки нельзя передавать непосредственно в SQL Server.

Доступны следующие варианты:

  1. Передача списка с разделителями-запятыми, а затем функция в SQL разбивает список. Список, разделенный запятыми, скорее всего, будет передан как Nvarchar ()
  2. Передайте xml и попросите функцию в SQL Server проанализировать XML для каждого значения в списке.
  3. Использовать новый определенный пользователем тип таблицы (SQL 2008)
  4. Динамически создайте SQL и передайте необработанный список как «1,2,3,4» и создайте оператор SQL. Это подвержено атакам с использованием SQL-инъекций, но будет работать.
Джон Рейнор
источник
2

Да, сделайте параметр Stored proc как VARCHAR(...) А затем передайте значения, разделенные запятыми, в хранимую процедуру.

Если вы используете Sql Server 2008, вы можете использовать TVP ( параметры значений таблицы ): SQL 2008 TVP и LINQ, если структура QueryTable более сложная, чем массив строк, в противном случае это было бы излишним, потому что требуется, чтобы тип таблицы был создан в SQl Server.

sll
источник
2

Сделайте datatable с одним столбцом вместо списка и добавьте строки в таблицу. Вы можете передать эту таблицу данных как структурированный тип и выполнить еще одно соединение с полем заголовка вашей таблицы.

голодный разум
источник
это путь. Я фактически создал таблицу на стороне db и загрузил bcp для записи на сервер.
dier
1

Если вы предпочитаете разбивать список CSV в SQL, есть другой способ сделать это с помощью общих табличных выражений (CTE). См. Раздел « Эффективный способ разделения строк с помощью CTE» .

Ларри Сильверман
источник
Спасибо. Вместо этого изменен на вопрос о переполнении стека.
Ларри Сильверман,
0

Единственный способ, о котором я знаю, - это создать список CSV и затем передать его как строку. Затем, на стороне SP, просто разделите его и делайте все, что вам нужно.

Андрей Агибалов
источник
0
CREATE TYPE [dbo].[StringList1] AS TABLE(
[Item] [NVARCHAR](MAX) NULL,
[counts][nvarchar](20) NULL);

создайте ТИП как таблицу и назовите его "StringList1"

create PROCEDURE [dbo].[sp_UseStringList1]
@list StringList1 READONLY
AS
BEGIN
    -- Just return the items we passed in
    SELECT l.item,l.counts FROM @list l;
    SELECT l.item,l.counts into tempTable FROM @list l;
 End

Создайте процедуру, как указано выше, и назовите ее "UserStringList1" s

String strConnection = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString.ToString();
            SqlConnection con = new SqlConnection(strConnection);
            con.Open();
            var table = new DataTable();

            table.Columns.Add("Item", typeof(string));
            table.Columns.Add("count", typeof(string));

            for (int i = 0; i < 10; i++)
            {
                table.Rows.Add(i.ToString(), (i+i).ToString());

            }
                SqlCommand cmd = new SqlCommand("exec sp_UseStringList1 @list", con);


                    var pList = new SqlParameter("@list", SqlDbType.Structured);
                    pList.TypeName = "dbo.StringList1";
                    pList.Value = table;

                    cmd.Parameters.Add(pList);
                    string result = string.Empty;
                    string counts = string.Empty;
                    var dr = cmd.ExecuteReader();

                    while (dr.Read())
                    {
                        result += dr["Item"].ToString();
                        counts += dr["counts"].ToString();
                    }

в С #, попробуйте это

Deepalakshmi
источник