Правильный метод удаления более 2100 строк (по идентификатору) с помощью Dapper

Я пытаюсь использовать Dapper для поддержки моего доступа к данным для моего серверного приложения.

В моем серверном приложении есть еще одно приложение, которое отправляет записи в мою базу данных со скоростью 400 записей в минуту.

Мое приложение извлекает их партиями, обрабатывает, а затем удаляет из базы данных.

Поскольку данные продолжают поступать в базу данных, пока я их обрабатываю, у меня нет хорошего способа сказать delete from myTable where allProcessed = true.

Однако я знаю значение PK удаляемых строк. Так что я хочу сделать delete from myTable where Id in @listToDelete

Проблема в том, что если мой сервер выйдет из строя хотя бы на 6 минут, мне нужно будет удалить более 2100 строк.

Поскольку Dapper берет мой @listToDelete и превращает каждый из них в параметр, мой вызов для удаления завершается ошибкой. (Из-за чего моя очистка данных еще больше отстает.)

Как лучше всего справиться с этим в Dapper?

ПРИМЕЧАНИЯ. Я просмотрел табличные значения параметров, но, насколько я вижу, они не очень эффективны. Эта часть моей архитектуры является узким местом моей системы, и мне нужно быть очень очень быстрым.


person Vaccano    schedule 30.03.2012    source источник
comment
@marc_s - мне не нужно передавать столько параметров... Но мне нужно удалить столько строк по идентификатору PK. Однако я делаю, что меня устраивает. Я говорю Dapper удалить каждую строку в моем @list. Это Dapper делает параметры каждого элемента в моем списке.   -  person Vaccano    schedule 30.03.2012
comment
Трудно сказать по этой небольшой информации, но почему вы не можете использовать критерии выбора для партии в качестве критериев для удаления. Или установите обработанный флаг в MyTable, установите его в процессе обработки и затем используйте его. Это не блестяще, но это будет намного быстрее, чем удалять их по одному. Это не будет шокирующе плохо даже с 10 000 записей.   -  person Tony Hopkinson    schedule 30.03.2012
comment
Мои критерии партии не очень эффективны. Так что я бы предпочел не использовать это. Я мог бы добавить флаг обработки, но для этого мне понадобился бы способ вызвать строки, в которые нужно добавить флаг обработки. Если я могу вызвать их, я могу также удалить их. (Я мог бы добавить флаг, чтобы сказать, пакетно в выбранное время. Но я бы не стал.)   -  person Vaccano    schedule 30.03.2012


Ответы (3)


Один из вариантов — создать временную таблицу на сервере, а затем использовать средство массовой загрузки для одновременной загрузки всех идентификаторов в эту таблицу. Затем используйте предложение join, EXISTS или IN, чтобы удалить только те записи, которые вы загрузили во временную таблицу.

Массовая загрузка — это хорошо оптимизированный путь в SQL Server, и он должен быть очень быстрым.

Например:

  1. Выполнить оператор CREATE TABLE #RowsToDelete(ID INT PRIMARY KEY)
  2. Используйте массовую загрузку, чтобы вставить ключи в #RowsToDelete
  3. Выполнить DELETE FROM myTable where Id IN (SELECT ID FROM #RowsToDelete)
  4. Выполните 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));
person Chris Smith    schedule 30.03.2012
comment
Добавленный пример кода, который фактически использует SqlBulkCopy, может быть адаптирован для более широких таблиц путем добавления дополнительных столбцов во временную таблицу и таблицу данных. - person Chris Marisic; 26.02.2015

Чтобы заставить этот код работать, я перешел на темную сторону.

Так как Даппер превращает мой список в параметры. И SQL Server не может обрабатывать множество параметров. (Мне никогда раньше не требовались даже двузначные параметры). Мне пришлось пойти с динамическим SQL.

Итак, вот мое решение:

string listOfIdsJoined = "("+String.Join(",", listOfIds.ToArray())+")";
connection.Execute("delete from myTable where Id in " + listOfIdsJoined);

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

  • Этот код работает на сервере, единственным вводом которого является поток данных из системы мейнфреймов.
  • Список, который я динамически создаю, представляет собой список longs/bigints.
  • Longs/bigints взяты из столбца Identity.

Я знаю, что построение динамического SQL — это плохо, но в данном случае я просто не понимаю, как это ведет к угрозе безопасности.

person Vaccano    schedule 30.03.2012
comment
@Pure.Krome, предположительно, его listOfIds имеет тип List<int>, поэтому нет необходимости дезинфицировать входные данные - person Zac; 13.11.2015
comment
Возможно... но предположения - корень всех зол. Во-вторых, это общее предложение => создание sql-скрипта, на лету и в строку... может очень плохо закончиться :) - person Pure.Krome; 14.11.2015

Dapper запрашивает список объектов, имеющих параметр в качестве свойства, поэтому в приведенном выше случае будет работать список объектов, имеющих идентификатор в качестве свойства.

connection.Execute("delete from myTable where Id in (@Id)", listOfIds.AsEnumerable().Select(i=> new { Id = i }).ToList());

Это сработает.

person Amit007    schedule 01.03.2016
comment
Это создаст отдельный запрос для каждого Id в listOfIds. - person Jakub Bezák; 12.04.2018