Правильный способ удаления более 2100 строк (по идентификатору) с помощью Dapper
Я пытаюсь использовать Dapper поддерживать мой доступ к данным для моего серверного приложения.
В моем серверном приложении есть другое приложение, которое записывает записи в мою базу данных со скоростью 400 в минуту.
Мое приложение вытаскивает их партиями, обрабатывает их, а затем удаляет из базы данных.
Поскольку данные продолжают поступать в базу данных во время обработки, у меня нет хорошего способа сказать delete from myTable where allProcessed = true
.
Однако, я знаю значение PK строк для удаления. Поэтому я хочу сделать delete from myTable where Id in @listToDelete
Проблема в том, что если мой сервер опустится даже на 6 минут, у меня будет более 2100 строк для удаления.
Так как Dapper берет мой @listToDelete и превращает каждый в параметр, мой вызов на удаление терпит неудачу. (Из-за того, что моя очистка данных еще больше позади.)
Каков наилучший способ справиться с этим в Dapper?
ПРИМЕЧАНИЯ:
Я просмотрел Tabled Valued Parameters, но из того, что я вижу, они не очень эффективны . Эта часть моей архитектуры - это горло бутылки моей системы, и мне нужно быть очень очень быстро.
Ответы
Ответ 1
Один из вариантов - создать временную таблицу на сервере, а затем использовать средство массовой загрузки для загрузки всех идентификаторов в эту таблицу одновременно. Затем используйте предложение join, EXISTS или IN, чтобы удалить только те записи, которые вы загрузили в свою временную таблицу.
Массовые загрузки - это хорошо оптимизированный путь в SQL Server, и он должен быть очень быстрым.
Например:
- Выполнить инструкцию
CREATE TABLE #RowsToDelete(ID INT PRIMARY KEY)
- Используйте массовую загрузку для вставки ключей в
#RowsToDelete
- Выполнить
DELETE FROM myTable where Id IN (SELECT ID FROM #RowsToDelete)
- Выполнить
DROP TABLE #RowsToDelte
(таблица также будет автоматически удалена, если вы закроете сеанс)
Пример кода (Предположим, Dapper):
conn.Open();
var columnName = "ID";
conn.Execute(string.Format("CREATE TABLE #{0}s({0} INT PRIMARY KEY)", columnName));
using (var bulkCopy = new SqlBulkCopy(conn))
{
bulkCopy.BatchSize = ids.Count;
bulkCopy.DestinationTableName = string.Format("#{0}s", columnName);
var table = new DataTable();
table.Columns.Add(columnName, typeof (int));
bulkCopy.ColumnMappings.Add(columnName, columnName);
foreach (var id in ids)
{
table.Rows.Add(id);
}
bulkCopy.WriteToServer(table);
}
//or do other things with your table instead of deleting here
conn.Execute(string.Format(@"DELETE FROM myTable where Id IN
(SELECT {0} FROM #{0}s", columnName));
conn.Execute(string.Format("DROP TABLE #{0}s", columnName));
Ответ 2
Чтобы заставить этот код работать, я пошел темной стороной.
Так как Dapper делает мой список в параметрах. И SQL Server не может обрабатывать множество параметров. (Мне никогда не нужны даже двузначные параметры). Мне пришлось идти с Dynamic SQL.
Итак, вот мое решение:
string listOfIdsJoined = "("+String.Join(",", listOfIds.ToArray())+")";
connection.Execute("delete from myTable where Id in " + listOfIdsJoined);
Прежде чем все хватают факелы и вилы, позвольте мне объяснить.
- Этот код выполняется на сервере, единственным входом которого является фид данных из системы Mainframe.
- Список, который я динамически создаю, представляет собой список longs/bigints.
- Longs/bigints из столбца Identity.
Я знаю, что построение динамического SQL - это плохой juju, но в этом случае я просто не вижу, как это приводит к угрозе безопасности.
Ответ 3
Dapper запрашивает список объектов, имеющих параметр как свойство, поэтому в приведенном выше случае будет работать список объектов, имеющих свойство Id as.
connection.Execute("delete from myTable where Id in (@Id)", listOfIds.AsEnumerable().Select(i=> new { Id = i }).ToList());
Это будет работать.