Ошибка конверсии при преобразовании nvarchar в тип данных int [duplicate]

Каков наилучший способ написать запрос с предложением IN с использованием Dapper ORM, когда список значений для предложения IN исходит из бизнес-логики? Например, допустим, у меня есть запрос:

SELECT * 
  FROM SomeTable 
 WHERE id IN (commaSeparatedListOfIDs)

В бизнес-логике commaSeparatedListOfIDs передается commaSeparatedListOfIDs, и это может быть любой тип IEnumerable(of Integer). Как я могу построить запрос в этом случае? Должен ли я делать то, что я делал до сих пор, что в основном представляет собой конкатенацию строк или есть какая-то техника расширенного параметрирования, о которой я не знаю?

150
задан 5 December 2011 в 22:44

7 ответов

Также убедитесь, что вы не завертываете круглые скобки вокруг строки запроса следующим образом:

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

У меня возникла ошибка SQL-синтаксиса с использованием Dapper 1.50.2, исправлена ​​путем удаления круглых скобок

[F2]
2
ответ дан 15 August 2018 в 14:39

В моем случае я использовал это:

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

моя переменная «ids» во второй строке является IEnumerable из строк, также они могут быть целыми числами, которые я думаю.

3
ответ дан 15 August 2018 в 14:39

Нет необходимости добавлять () в предложение WHERE, которое мы обычно делаем, потому что Dapper делает это автоматически для вас. Вот syntax: -

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

var conditions = new { listOfIntegers };

var results = connection.Query(SQL, conditions);
3
ответ дан 15 August 2018 в 14:39

Возможно, это самый быстрый способ запросить большое количество строк с помощью 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;
   }
}

Имейте в виду, что вам нужно научиться немного о Bulk Inserts. Существуют варианты запуска триггеров (по умолчанию нет), соблюдение ограничений, блокировка таблицы, возможность одновременных вставок и т. Д.

10
ответ дан 15 August 2018 в 14:39
  • 1
    Да, я согласен с вашей общей идеей создания таблицы temp с идентификаторами и последующим внутренним присоединением к этой таблице. Мы сделали это внутренне, и это значительно улучшило производительность запросов. Я не уверен, что я бы использовал класс DataTable для чего-либо, но ваше решение полностью допустимо. Это намного быстрее. – Marko 26 January 2017 в 23:16
  • 2
    Для объемной вставки требуется DataTable. Как вы вставляете в временную таблицу 50 000 значений? – ErikE 27 January 2017 в 00:23
  • 3
    В кусках 1000, если я правильно помню предел? Во всяком случае, я не знал, что вы можете обойти предел с помощью DataTable, поэтому я узнал что-то новое сегодня ... – Marko 27 January 2017 в 06:39
  • 4
    [F1] является только предпосылкой для объемной вставки. Массовые вставки являются особыми. Они выполняются в специальном режиме, который делает их намного быстрее, чем обычная вставка. – ErikE 27 January 2017 в 07:10
  • 5
    Это смехотворная работа, когда вы можете использовать параметр значения таблицы вместо этого. Dapper полностью поддерживает передачу DataTable в виде TVP, что позволяет обойтись без создания и уничтожения таблицы temp, а также заполнить эту временную таблицу через BulkCopy. Мы регулярно используем решение на основе TVP в случаях, когда количество параметров для предложения IN будет слишком большим. – Mr. T 28 March 2017 в 23:46

Непосредственно с главной страницы проекта 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
47
ответ дан 15 August 2018 в 14:39

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

В Интернете есть много функций сплиттера, вы будете легко найти любой, если ваш вкус 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

(или аналогичный)

2
ответ дан 15 August 2018 в 14:39

Если ваше предложение IN слишком велико для обработки MSSQL, вы можете легко использовать TableValueParameter с Dapper.

Создайте свой TVP-тип в MSSQL:
CREATE TYPE [dbo].[MyTVP] AS TABLE([ProviderId] [int] NOT NULL)
Создайте DataTable с тот же столбец (-ы) как TVP и заполнить его значениями
var tvpTable = new DataTable();
tvpTable.Columns.Add(new DataColumn("ProviderId", typeof(int)));
// fill the data table however you wish
Измените свой запрос Dapper, чтобы сделать INNER JOIN в таблице TVP:
var query = @"SELECT * FROM Providers P
    INNER JOIN @tvp t ON p.ProviderId = t.ProviderId";
Передайте DataTable в запросе запроса Dapper
sqlConn.Query(query, new {tvp = tvpTable.AsTableValuedParameter("dbo.MyTVP")});

Это также фантастически работает, когда вы хотите сделать массовое обновление нескольких столбцов - просто создайте TVP и выполните UPDATE с внутренним соединением с TVP.

20
ответ дан 15 August 2018 в 14:39
  • 1
    Это лучший способ, если у вас слишком много параметров. – 1.44mb 11 June 2017 в 12:24
  • 2
    Отличное решение, однако, не работает на .Net Core, см. Этот вопрос: stackoverflow.com/questions/41132350/… . Также см. Эту страницу: github.com/StackExchange/Dapper/issues/603 – pcdev 18 October 2017 в 09:51
  • 3
    Вы также можете подумать о том, чтобы сделать ProviderId на MyTVP равным PRIMARY KEY CLUSTERED, так как это просто решило проблему производительности для нас (значения, которые мы проходили, не содержали дубликатов). – Richardissimo 18 July 2018 в 13:33

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

Похожие вопросы: