Как вручную заблокировать и разблокировать таблицу, чтобы предотвратить вставки - PullRequest
1 голос
/ 14 марта 2019

Приведенный ниже код работает, но не мешает другому пользователю вставить строку и, таким образом, создать дубликат ID.
Идентификаторы обновляемой таблицы автоматически увеличиваются и присваиваются. В коде ниже я делаю следующее:

Получить следующий доступный идентификатор (nextID)

Установите идентификатор каждой сущности в nextID ++

Массовая вставка

Как заблокировать таблицу, чтобы другой пользователь не мог вставить ее во время выполнения трех указанных выше задач? Я видел похожие вопросы, в которых предлагается установка ISOLATIONLEVEL READCOMMITTED, но я не думаю, что это заблокирует таблицу в тот момент, когда я получаю nextID.

public void BulkInsertEntities(List<Entity> entities)
{
    if (entities == null)
        throw new ArgumentNullException(nameof(entities));

    string tableName = "Entities";

    // -----------------------------------------------------------------
    // Prevent other users from inserting (but not reading) here
    // -----------------------------------------------------------------

    long lastID = GetLastID(tableName);
    entities.ForEach(x => x.ID = lastID++);

    using (SqlConnection con = new SqlConnection(db.Database.GetDbConnection().ConnectionString))
    {
        con.Open();

        using (SqlBulkCopy bulkCopy = new SqlBulkCopy(con.ConnectionString, SqlBulkCopyOptions.KeepIdentity))
        {
            bulkCopy.DestinationTableName = tableName;
            DataTable tbl = DataUtil.ToDataTable<Entity>(entities);

            foreach (DataColumn col in tbl.Columns)
                bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);

            bulkCopy.WriteToServer(tbl);
        }
    }

    // ---------------------------
    // Allow other users to insert
    // ---------------------------
}

protected long GetLastID(string tableName)
{
    long lastID = 0;

    using (var command = db.Database.GetDbConnection().CreateCommand())
    {
        command.CommandText = $"SELECT IDENT_CURRENT('{tableName}') + IDENT_INCR('{tableName}')";
        db.Database.OpenConnection();
        lastID = Convert.ToInt64(command.ExecuteScalar());
    }
    return lastID;
}

1 Ответ

0 голосов
/ 14 марта 2019

Для идентичности функциональности с вариантом гибкости вы можете создать именованную последовательность:

create sequence dbo.MySequence as int

... и иметь ограничение по умолчанию для таблицы: default(next value for dbo.MySequence).

Приятно то, что вы можете «записывать» идентификаторы и отправлять их клиентам, чтобы у них был ключ, который они могут вставить в свои данные ... а затем, когда данные поступают предварительно заполненными, никакого вреда нет. фол. Требуется немного больше работы, чем поля идентификации, но это не так уж страшно. Под «записью» я подразумеваю, что вы можете получить новый идентификатор в любое время, позвонив по номеру next value for dbo.MySequence в любом месте. Если вы удерживаете это значение, вы знаете, , что оно не будет присвоено таблице. Таблица получит следующее значение после вашего. Затем вы можете на досуге вставить строку со значением, которое вы получили и удержали ... зная, что это законный ключ.

Существует функция блокировки вызовов приложений SQL Server. Я редко видел его использованным, но ваш пример может подойти. По сути, идея заключается в том, что вы бы помещали триггеры в таблицы, которые начинаются с тестирования выдающегося app_lock:

if ( applock_test( 'public', 'MyLock', 'Exclusive' ) = 1 )
begin
  raiserror( ... )
  return
  --> or wait and retry
end 

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

exec @rc = get_applock @dbPrincipal='public', @resource='MyLock', @lockMode='Exclusive'
if ( @rc = 0 )
begin
  --> got the lock, do the damage...
  --> and then, after carefully handling the edge cases,
  --> and making sure we dont skip the release...
  exec release_applock @resource='MyLock' @dbPrincipal='public'
end

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...