Правильный способ удаления более 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());

Это будет работать.