Подтвердить что ты не робот

Исключение OptimisticConcurrencyException не работает в инфраструктуре Entity в определенных ситуациях

UPDATE (2010-12-21): Полностью переписал этот вопрос на основе тестов, которые я делал. Кроме того, это был конкретный вопрос POCO, но, оказывается, мой вопрос не обязательно специфичен для POCO.

Я использую Entity Framework, и у меня есть столбец timestamp в моей таблице базы данных, который должен использоваться для отслеживания изменений для оптимистического concurrency. Я установил режим concurrency для этого свойства в Entity Designer на "Fixed", и я получаю противоречивые результаты. Вот несколько упрощенных сценариев, которые демонстрируют, что проверка concurrency работает в одном сценарии, но не в другом.

Успешно выбрасывает исключение OptimisticConcurrencyException:

Если я присоединю отключенный объект, то SaveChanges выкинет исключение OptimisticConcurrencyException, если есть конфликт временной метки:

    [HttpPost]
    public ActionResult Index(Person person) {
        _context.People.Attach(person);
        var state = _context.ObjectStateManager.GetObjectStateEntry(person);
        state.ChangeState(System.Data.EntityState.Modified);
        _context.SaveChanges();
        return RedirectToAction("Index");
    }

Не бросает исключение OptimisticConcurrencyException:

С другой стороны, если я получаю новую копию своего объекта из базы данных, и я делаю частичное обновление в некоторых полях, а затем вызываю SaveChanges(), то даже если есть конфликт временной метки, я не получить исключение OptimisticConcurrencyException:

    [HttpPost]
    public ActionResult Index(Person person) {
        var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
        currentPerson.Name = person.Name;

        // currentPerson.VerColm == [0,0,0,0,0,0,15,167]
        // person.VerColm == [0,0,0,0,0,0,15,166]
        currentPerson.VerColm = person.VerColm;

        // in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166]
        // in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167]
        _context.SaveChanges();
        return RedirectToAction("Index");
    }

На основе SQL Profiler выглядит так: Entity Framework игнорирует новый VerColm (который является свойством timestamp) и вместо этого использует первоначально загруженный VerColm. Из-за этого он никогда не будет генерировать исключение OptimisticConcurrencyException.


ОБНОВЛЕНИЕ: добавление дополнительной информации в запрос за январь:

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

Это значение VerColm в моей базе данных до обновления: 0x0000000000000FA7

Вот что показывает SQL Profiler при выполнении обновления:

exec sp_executesql N'update [dbo].[People]
set [Name] = @0
where (([Id] = @1) and ([VerColm] = @2))
select [VerColm]
from [dbo].[People]
where @@ROWCOUNT > 0 and [Id] = @1',N'@0 nvarchar(50),@1 int,@2 binary(8)',@0=N'hello',@1=1,@2=0x0000000000000FA7

Обратите внимание, что @2 должно было быть 0x0000000000000FA6, но оно 0x0000000000000FA7

Вот VerColm в моей базе данных после обновления: 0x0000000000000FA8


Кто-нибудь знает, как я могу обойти эту проблему? Я бы хотел, чтобы Entity Framework выдавал исключение, когда я обновляю существующий объект и там конфликтует временная метка.

Спасибо

4b9b3361

Ответ 1

Объяснение

Причина, по которой вы не получаете ожидаемый OptimisticConcurrencyException в своем втором примере кода, объясняется тем, как EF проверяет concurrency:

Когда вы извлекаете объекты, запрашивая ваш db, EF запоминает значение всех с ConcurrencyMode.Fixed отмеченными свойствами к моменту запроса в качестве исходных неизмененных значений.

Затем вы меняете некоторые свойства (включая Fixed отмеченные) и вызываете SaveChanges() в свой DataContext.

EF проверяет параллельные обновления, сравнивая текущие значения всех столбцов Fixed с отмеченными столбцами db с исходными неизмененными значениями отмеченных свойств Fixed. Ключевым моментом здесь является то, что EF рассматривает обновление вашего свойства timestamp как нормальное обновление свойств данных. Поведение, которое вы видите, по дизайну.

Решение/Обход

Чтобы обходной путь, у вас есть следующие параметры:

  • Используйте свой первый подход: не требуйте db для своей сущности, но присоедините воссозданный объект к вашему контексту.

  • Подтвердите свое значение временной метки как текущее значение db, так что проверка EF concurrency использует ваше поставленное значение, как показано ниже (см. также этот ответ по аналогичному вопросу):

    var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
    currentPerson.VerColm = person.VerColm; // set timestamp value
    var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson);
    ose.AcceptChanges();       // pretend object is unchanged
    currentPerson.Name = person.Name; // assign other data properties
    _context.SaveChanges();
    
  • Вы можете сами проверить concurrency, сравнив значение временной метки с запрограммированным значением временной метки:

    var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
    if (currentPerson.VerColm != person.VerColm)
    {
        throw new OptimisticConcurrencyException();
    }
    currentPerson.Name = person.Name; // assign other data properties
    _context.SaveChanges();
    

Ответ 2

Вот еще один подход, который является более общим и подходит для уровня данных:

// if any timestamps have changed, throw concurrency exception
var changed = this.ChangeTracker.Entries<>()
    .Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
        x.OriginalValues.GetValue<byte[]>("Timestamp")));
if (changed) throw new OptimisticConcurrencyException();
this.SaveChanges();

Он просто проверяет, изменилось ли TimeStamp и выбрасывает исключение concurrency.

Ответ 3

Если это сначала код EF, используйте код, похожий на код ниже. Это изменит исходный TimeStamp, загруженный с db на один из пользовательского интерфейса, и обеспечит OptimisticConcurrencyEception.

db.Entry(request).OriginalValues["Timestamp"] = TimeStamp;