C # SQL Server - передача списку до збереженої процедури


111

Я викликаю збережену процедуру 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();

Мій збережений Proc є досить стандартним, але приєднується QueryTable(отже, потреба у використанні збереженої програми).

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

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

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

Чи є спосіб передати список рядків List<string>до збереженого файлу ??? Або є кращий спосіб зробити це?

Велике спасибі, Бретт



Яка версія SQL Server ?? 2005 ?? 2008 ?? 2008 R2 ?? SQL Server 2008 і новіші має поняття "параметри, що оцінюються за таблицею" (детальніше див. Відповідь Redth)
marc_s

Непов’язана порада: SqlDataReader також IDisposable, тому він повинен знаходитися в usingблоці.
Річардіссімо

Відповіді:


178

Якщо ви використовуєте 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

10
Чи потрібно визначати таблицю даних для встановлення значення параметра? Будь-який інший легкий підхід?
ca9163d9

2
Ми спробували це, але виявили, що його недолік не підтримується рамкою Enitiy
Bishoy Hanna

7
Будьте уважні з цим рішенням, якщо ви користуєтесь LINQ2SQL, ЯКЩО НЕ ПІДТРИМУЄТЬСЯ ВИКОРИСТАНІ ПОЛОЖИТЕЛЯМИ, визначені типовими таблицями, як параметри !!! Вирішення можна знайти у відповіді Джона Рейнора, використовуючи списки, розділені комами та функцію парсера, однак у цього є і недоліки ....
Фазі

3
@Fazi в цьому випадку не використовуйте Linq2SQL. Переважно об'єднання рядків і розбір рядків у T-SQL
Panagiotis Kanavos

2
Так, так як ви насправді виконуєте це з SSMS?
Сінестетичний

21

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

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

І тоді ви можете використовувати його в інших запитах.


17
дозвольте мені передати посилання на реалізацію dbo.SplitCommaString для повноти: goo.gl/P9ROs
Велі Гебрев

3
А що станеться, коли в одному з ваших полів даних є кома?
Кевін Панько

3
Розділіть її замість труб.
Алекс У Парижі

@AlexInParis Що робити, якщо в одному з ваших полів даних є труба?
RayLoveless

2
Потім використовуйте те, чого немає у ваших полях даних. Очистіть свої дані, якщо потрібно, але не так багато даних, які я коли-небудь бачив, коли-небудь використовував труби. Якщо вони абсолютно необхідні, знайдіть якийсь інший символ, наприклад ¤ або §.
Алекс В Парижі

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


2

Створіть таблицю даних за допомогою одного стовпчика замість списку та додайте рядки до таблиці. Ви можете передати цю таблицю даних як структурований тип і виконати ще одне з'єднання з полем заголовка таблиці.


це шлях. Я фактично створив таблицю на стороні db і завантажив запис на bcp на сервер.
Dier

1

Якщо ви віддаєте перевагу поділу списку CSV у SQL, існує інший спосіб зробити це за допомогою загальних табличних виразів (CTE). Див. Ефективний спосіб розділення рядків за допомогою CTE .


Дякую. Натомість замінено на питання переповнення стека.
Ларрі Сільверман

0

Єдиний спосіб, про який я знаю, - це скласти список CSV і потім передавати його як рядок. Потім, на стороні СП, просто розділіть його і зробіть все, що вам потрібно.


0
CREATE TYPE [dbo].[StringList1] AS TABLE(
[Item] [NVARCHAR](MAX) NULL,
[counts][nvarchar](20) NULL);

створити TYPE у вигляді таблиці та назвати його як "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"

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

у c #, Спробуйте це

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.