3

У меня есть таблица, в которой содержится информация о машинах (назовем ее tbl_incoming_car). В этой таблице есть уникальный столбец с именем «customer_number», который показывает количество автомобилей, которые попали в систему до сих пор. Один и тот же автомобиль может входить и выходить много раз, но это регистрируется только один раз.Запирание кодом EF Сначала

Итак, когда вступает новый автомобиль, мне нужно получить номер последнего, увеличить его, а затем сохранить его как «customer_number» нового автомобиля.

Я знаю, что самым простым способом было бы иметь отделенную таблицу для автомобилей, иметь «customer_number» и регистрировать входы и выходы в другой таблице, но это всего лишь глупый пример, чтобы разоблачить ситуацию. Поэтому нет смысла обсуждать, что подход ошибочен, я знаю это уже :)

Как я уже сказал, каждый раз, когда новый автомобиль входит в систему, я должен получить последнюю добавленную строку, получить «customer_number» ', увеличьте его и сохраните как атомную операцию. Другие экземпляры приложений могут попытаться сделать то же самое, и БД должна содержать запросы для последней добавленной строки во время «задачи создания».

Я думал, что смогу сделать это, установив уровень изоляции на сериализуемый, но я думаю, что это не помешает прочитать последнюю строку, а вставить новую. Таким образом, похоже, что блокировка - это решение. Я попытался использовать статический объект в качестве Монитора в коде, и он отлично работает, но, конечно, он ограничен одним и тем же доменом приложения, мне нужно что-то на уровне БД.

Я не думаю, что в EF есть что-то, чтобы установить блокировку на БД, так что это лучший способ установить блокировку на столе и выпустить ее позже?

Спасибо.

ответ

1

До сих пор это самый лучший способ, который я придумал :

public void SetTransactionLock(String resourceName) 
    { 
     Ensure.IsNotNull(resourceName, "resourceName"); 

     String command = String.Format(
     @"declare @result int; 
      EXEC @result = sp_getapplock '{0}', 'Exclusive', 'Transaction', 10000 
      IF @result < 0 
      RAISERROR('ERROR: cannot get the lock [{0}] in less than 10 seconds.', 16, 1);",resourceName); 

     base.Database.ExecuteSqlCommand(command); 
    } 

    public void ReleaseTransactionLock(String resourceName) 
    { 
     Ensure.IsNotNull(resourceName, "resourceName"); 
     String command = String.Format("EXEC sp_releaseapplock '{0}';",resourceName); 
     base.Database.ExecuteSqlCommand(command); 
    } 

Поскольку в EF нет встроенного способа, я добавил эти два метода к своему слою данных, и я использую их для объявления «критического раздела», где допускается только одна параллельная операция.

Вы можете использовать его в блоке try try.

+0

Спасибо за -1, но я бы хотел, чтобы причина указана. – vtortola

+0

Я не могу дать вам причину, но вот мой +1 для вас вместо – galets

+0

спасибо @galets :) – vtortola

0

EF поддерживает SQL timeStamp, также известный как rowversion dataType.

Это позволяет выполнять обновления с использованием оптимистической блокировки. Структура делает это для вас, если поля объявлены правильно. По существу:

ОБНОВЛЕНИЕ, где ID = ID SET CUSTOMER_NUMBER, чтобы Х + 1 становится Обновление, где ID = идентификатор и Rowversion = rowversion Набор CUSTOMER_NUMBER = Х + 1

Так, если строка изменилась поскольку он был загружен в поток X, вы получаете сообщение об ошибке. Вы можете перезагрузить и повторить попытку или принять то, что когда-либо было подходящим в вашем сценарии.

форма подробнее см, http://msdn.microsoft.com/en-us/data/jj592904

и

http://msdn.microsoft.com/en-us/library/aa0416cz.aspx

и это ТАК размещать

What is a good method to determine when an entities data has been changed on the database?

+0

Я говорю о вставке, а не обновлении. Никто не собирается обновлять строку, это просто вставка строк. – vtortola

+0

как вы можете вставить блокировку? то источником ключа является проблема. Сгенерированный БД ключ или указатель будет работать. Что вы используете для создания «уникального» ключа. Звучит странно для меня. –

+0

используя pesimistic параллелизм, так как я собираюсь опубликовать сейчас – vtortola

1

Последовательный процесс действительно решает эту проблему. Serializable означает, что транзакции ведут себя так, как если бы они взяли глобальную Х-блокировку базы данных. Как будто только одна транзакция выполняется одновременно.

Это справедливо и для вставок. Блокировки будут выполняться таким образом, чтобы предотвратить попадание вставок в неправильные места.

Возможно, вы не достигнете тупиковой свободы. Еще стоит попробовать.

+0

Я пробовал, но у меня проблемы с тупиками. Я не очень уверен в том, как работает serializable. Например, если я делаю SELECT, чтобы получить последнюю строку перед INSERT, будущие строки, которые могут появляться в SELECT, не могут быть прочитаны? Поскольку приложение считывает последнюю строку, обрабатывает значение, а затем сохраняет новую строку. Я понимаю, что «serializable» может помешать приложению вставить строку, но не читать последнюю. – vtortola

1

Использование уровня REPEATABLE READ для изоляции может быть заблокировано в момент выполнения команды SELECT. Как я не знаю, тогда команда SELECT для извлечения последней строки, может быть, она не будет работать. То, как вы выполняете SELECT, меняет способ SQL Locks Lines/Indexes/Tables.

Лучшим aprroach будет выполнение хранимой процедуры. EF делает несколько обращений между базой данных и приложением, что увеличит время блокировки и повлияет на производительность вашего приложения.

Возможно, вы могли бы использовать триггер для обновления после вставки.

+0

Правильно, но мы пытаемся обойтись без SP, пока не будет ясно, что мы должны делать (динамические требования, вы знаете: P). На данный момент мы проходим большие циклы рефакторинга, и SP будет мешать. – vtortola

+0

EF не даст вам много возможностей для управления транзакцией по SQL. Раньше я использовал только EF, но для производительности я был вынужден нарушить правило. Во всем приложении, всего лишь около 5 SP, для очень специфичных транзакций с высокой производительностью. –

1

EF работает с C# TransactionScope.

В прошлом, я решала подобную проблему следующим образом:

using (var ts = new TransactionScope()) 
     { 
      using (var db = new DbContext()) 
      { 
       db.Set<Car>().Add(newCar); 
       db.SaveChanges(); 
       // newCar.Id has value here, but record is locked for read until ts.Complete() 

       newCar.CustomerNumber = db.Set<Car>().Max(car => car.CustomerNumber) + 1; 
       db.SaveChanges(); 
      } 
      ts.Complete(); 
     } 

линии db.Set<Car>().Max(car => car.CustomerNumber) в любых параллельных задач придется ждать, потому что он должен иметь доступ ко всем записям, вы можете проверить это добавив точку останова до ts.Complete() и попытавшись выполнить Select max(CustomerNumber) from dbname.dbo.Cars, в то время как код приостановлен на этой строке. Запрос завершится, когда вы возобновите выполнение кода и ts.

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

Для получения дополнительной информации см. transactions in ef on MSDN.

+0

Посмотрите, что, хотя есть транзакция, мне нужно заблокировать другие потоки от чтения данных, что не может быть сделано с помощью простой транзакции. Транзакция на уровне «сериализации» не позволит другим потокам добавлять строку, соответствующую данному предикату, но не читать ее. – vtortola

+0

Не понимаю. По умолчанию транзакция, подобная этой, предотвратит чтение других строк из новой строки (newCar), и если вы только вставляете записи с этим методом, они будут ждать завершения транзакции, чтобы иметь возможность читать эту запись, и добавьте новый. –

1

Решение, ранее упомянутое в этой теме, может быть не универсальным, поскольку EF запускает sp_resetconnection перед любой операцией SQL, поэтому, когда вы запускаете что-либо, возвращающее SQL в DbContext, оно по существу освобождает блокировку, которую вы должны были удерживать.

То, что я придумал: вы должны клонировать SqlConnection до получения блокировки, и держаться этой связи, пока не будут готовы к выпуску:

public class SqlAppLock { 
    DbConnection connection; 
    public SqlAppLock(DbContext context, string resourceName) 
    { 
     ... (test arguments for null, etc) 
     connection = (DbConnection)(context.Database.Connection as ICloneable).Clone(); 
     connection.Open(); 

     var cmd = connection.CreateCommand(); 
     cmd.CommandText = "sp_getapplock"; 
     ... (set up parameters) 
     cmd.ExecuteNonQuery(); 

     int result = (int)cmd.Parameters["@result"].Value; 
     if (result < 0) 
     { 
      throw new ApplicationException("Could not acquire lock on resource"); 
     } 
    } 

, а затем отпустить, может снять блокировку используя sp_releaseapplock, или просто удалите() соединение

Смежные вопросы