简体   繁体   中英

How to manually lock and unlock table such that inserts are prevented

The code below works but does not prevent a different user from inserting a row and thus creating a duplicate ID.
The IDs for the table being updated are auto incremented and assigned. In the code below I do the following:

Get the next available ID (nextID)

Set the ID of each entity to nextID++

Bulk insert

How do I lock the table such that another user cannot insert while the the three tasks above are running? I have seen similar questions that propose setting ISOLATIONLEVEL READCOMMITTED however I don't think that will lock the table at the time I am getting the 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;
}

For identity-like functionality with a variant on the flexibility, you can create a named sequence:

create sequence dbo.MySequence as int

...and have a default constraint on the table: default(next value for dbo.MySequence) .

Nice thing about this is that you can "burn" IDs and send them to clients so they have a key they can put into their data...and then, when the data comes in pre-populated, no harm, no foul. It takes a little more work than identity fields, but it's not too terrible. By "burn" I mean you can get a new ID anytime by calling next value for dbo.MySequence anywhere you like. If you hold onto that value, you know it's not going to be assigned to the table. The table will get the next value after yours. You can then, at your leisure insert a row with the value you got and held...knowing it's a legit key.

There is a feature in SQL Server call application locks. I've only rarely seen it used, but your example might be suitable. Basically, the idea is that you'd put triggers on tables that start by testing for an outstanding app_lock:

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

...and the long-running process that can't be interrupted gets the applock at the beginning and releases it at the 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

There are lots of variations. Session-based locks which can be auto-released when a session ends (beware of connection pooling), timeouts, multiple lock modes (shared, exclusive, etc.), and scoped locks (that may not apply to privileged db users).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM