ВИБІРТИ * ІЗ X, де ідентифікується (…) з Dapper ORM


231

Який найкращий спосіб написати запит із пунктом IN за допомогою Dapper ORM, коли список значень для пункту IN походить із ділової логіки? Наприклад, скажімо, у мене є запит:

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

Це commaSeparatedListOfIDsпередається з ділової логіки, і це може бути будь-який тип IEnumerable(of Integer). Як би я сконструював запит у цьому випадку? Чи потрібно мені робити те, що я робив до цих пір, це в основному рядкове конкатенація або є якась вдосконалена техніка відображення параметрів, про яку я не знаю?

Відповіді:


366

Dapper підтримує це безпосередньо. Наприклад...

string sql = "SELECT * FROM SomeTable WHERE id IN @ids"
var results = conn.Query(sql, new { ids = new[] { 1, 2, 3, 4, 5 }});

47
Я думаю, що важливо зазначити, що існує обмежене обмеження кількості елементів, які ви можете надіслати у своєму масиві. Я зрозумів це важким шляхом, коли пройшов занадто багато ідентифікаторів. Я не пам'ятаю точну кількість, але з моєї пам'яті, я думаю, що це 200 елементів, перш ніж Dapper перестане працювати / виконувати запит.
Марко

8
Марко, це важливо. І якщо ви робите це таким чином, ви можете розглянути можливість пошуку іншого способу запиту ваших даних, наприклад, як зробити приєднання чи анти-приєднання, а не передавати список ідентифікаторів. Становище IN не є найбільш високоефективним запитом, і його часто можна замінити існуючим пунктом, який буде швидшим.
Дон Ролінг

24
FYI - SQL Server 2008 R2 має обмеження до 2100 записів у INпункті.
Джессі

6
А SQLite має обмеження за замовчуванням у 999 змінних.
Камерон

8
Будьте обережні: у SQL Server це не вдається, якщо у вашому масиві є кілька елементів, і ви загортаєте параметр у дужки. Видалення дужок вирішить проблему.
ajbeaven

66

Прямо з домашньої сторінки проекту GitHub :

Dapper дозволяє вам передати IEnumerable і автоматично параметризує ваш запит.

connection.Query<int>(
    @"select * 
      from (select 1 as Id union all select 2 union all select 3) as X 
      where Id in @Ids", 
    new { Ids = new int[] { 1, 2, 3 });

Буде перекладено на:

select * 
from (select 1 as Id union all select 2 union all select 3) as X 
where Id in (@Ids1, @Ids2, @Ids3)

// @Ids1 = 1 , @Ids2 = 2 , @Ids2 = 3

43

Якщо ваш INпункт занадто великий для обробки MSSQL, ви можете легко використовувати TableValueParameter з Dapper.

  1. Створіть свій тип TVP в MSSQL:

    CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
  2. Створіть DataTableстовпець із тими ж стовпцями, що і TVP, і заповніть його значеннями

    var tvpTable = new DataTable();
    tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
    // fill the data table however you wish
  3. Змініть запит Dapper, щоб він робив INNER JOINна таблиці TVP:

    var query = @"SELECT * FROM Providers P
        INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
  4. Передайте таблицю даних у своєму дзвінку на запит Dapper

    sqlConn.Query(query, new {tvp = tvpTable.AsTableValuedParameter("dbo.MyTVP")});

Це також фантастично працює, коли ви хочете зробити масове оновлення кількох стовпців - просто побудуйте TVP і зробіть UPDATEвнутрішнє приєднання до TVP.


Чудове рішення, однак не працює у .Net Core, дивіться це питання: stackoverflow.com/questions/41132350/… . Також дивіться цю сторінку: github.com/StackExchange/Dapper/isissue/603
pcdev

3
Ви можете також розглянути питання про внесення ProviderIdна MyTVPбути PRIMARY KEY CLUSTERED, так як це просто вирішити проблему продуктивності для нас (значень , які ми не проходили утримуємо ніяких дублікатів).
Річардіссімо

@Richardissimo Чи можете ви показати приклад того, як це зробити? Я, здається, не можу правильно виправити синтаксис.
Майк Коул


14

Тут, можливо, найшвидший спосіб запиту великої кількості рядків з Dapper, використовуючи список ідентифікаторів. Я обіцяю вам, що це швидше, ніж майже будь-який інший спосіб, який ви можете придумати (за винятком можливого використання TVP, як зазначено в іншій відповіді, і який я не перевіряв, але я підозрюю, що це може бути повільніше, тому що ви все ще повинні заповнити ТВП). Це планети швидше, ніж Dapper, використовуючи INсинтаксис і всесвітлюється швидше, ніж Entity Framework, рядок. І це навіть континенти швидше, ніж проходження у списку VALUESабо UNION ALL SELECTпредметів. Його можна легко розширити, використовуючи клавішу з декількома стовпцями, просто додайте додаткові стовпці до DataTable, темп-таблиці та умовам об’єднання.

public IReadOnlyCollection<Item> GetItemsByItemIds(IEnumerable<int> items) {
   var itemList = new HashSet(items);
   if (itemList.Count == 0) { return Enumerable.Empty<Item>().ToList().AsReadOnly(); }

   var itemDataTable = new DataTable();
   itemDataTable.Columns.Add("ItemId", typeof(int));
   itemList.ForEach(itemid => itemDataTable.Rows.Add(itemid));

   using (SqlConnection conn = GetConnection()) // however you get a connection
   using (var transaction = conn.BeginTransaction()) {
      conn.Execute(
         "CREATE TABLE #Items (ItemId int NOT NULL PRIMARY KEY CLUSTERED);",
         transaction: transaction
      );

      new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, transaction) {
         DestinationTableName = "#Items",
         BulkCopyTimeout = 3600 // ridiculously large
      }
         .WriteToServer(itemDataTable);
      var result = conn
         .Query<Item>(@"
            SELECT i.ItemId, i.ItemName
            FROM #Items x INNER JOIN dbo.Items i ON x.ItemId = i.ItemId
            DROP TABLE #Items;",
            transaction: transaction,
            commandTimeout: 3600
         )
         .ToList()
         .AsReadOnly();
      transaction.Rollback(); // Or commit if you like
      return result;
   }
}

Будьте в курсі, що вам потрібно трохи дізнатися про масові вставки. Існують варіанти щодо запуску тригерів (за замовчуванням немає), з дотриманням обмежень, блокування таблиці, дозволення одночасних вставок тощо.


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

DataTableПотрібно для масової вставки. Як ви вставляєте в таблицю темп 50000 значень?
ЕрікЕ

1
У шматках 1000, якщо я правильно пам'ятаю межу? У всякому разі, я не знав, що ви можете обійти ліміт за допомогою DataTable, тому сьогодні я дізнався щось нове ...
Marko

1
Це смішна кількість роботи, коли ви могли замість цього використовувати параметр таблиці. Dapper чітко підтримує передачу DataTable у якості TVP, що дозволяє відмовитися від створення та знищення тимчасової таблиці, а також заповнити цю таблицю темп через BulkCopy. Ми використовуємо рішення на основі TVP звичайно у випадках, коли кількість параметрів для пункту IN буде занадто великою.
Містер Т

3
Це не смішна кількість роботи, особливо якщо хтось трохи її абстрагує хелперним класом або методом розширення.
ErikE

11

Також переконайтеся, що ви не обертаєте дужки навколо рядка запиту так:

SELECT Name from [USER] WHERE [UserId] in (@ids)

У мене виникла помилка синтаксису SQL із використанням Dapper 1.50.2, виправлена ​​видаленням дужок

SELECT Name from [USER] WHERE [UserId] in @ids

7

Це не потрібно , щоб додати ()в реченні WHERE , як ми робимо в звичайному SQL. Тому що Dapper робить це автоматично для нас. Ось syntax:

const string SQL = "SELECT IntegerColumn, StringColumn FROM SomeTable WHERE IntegerColumn IN @listOfIntegers";

var conditions = new { listOfIntegers };

var results = connection.Query(SQL, conditions);


3

У моєму випадку я використав це:

var query = "select * from table where Id IN @Ids";
var result = conn.Query<MyEntity>(query, new { Ids = ids });

мій змінний "ідентифікатор" у другому рядку - це ряд IE безлічі рядків, також, мабуть, вони можуть бути цілими числами.


List<string>?
Кікенет

2

На мій досвід, найдобріший спосіб вирішення цього питання - це функція, яка перетворює рядок у таблицю значень.

В Інтернеті доступно багато функцій спліттера, ви легко знайдете їх на будь-який, якщо ваш смак SQL.

Потім ви можете зробити ...

SELECT * FROM table WHERE id IN (SELECT id FROM split(@list_of_ids))

Або

SELECT * FROM table INNER JOIN (SELECT id FROM split(@list_of_ids)) AS list ON list.id = table.id

(Або подібне)

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