Параллелизм через ограниченный DbContext - PullRequest
0 голосов
/ 02 октября 2018

У меня есть стандартный веб-API ASP.NET Core 2.0 со стандартным DI DbContext как таковой:

services.AdDbContext<TestContext>( ... );

Я знаю, что мой контекст будет добавлен с областью действия , чтопо сути, означает, что каждый раз, когда мой API получает новый запрос, он создает новый экземпляр контекста.

Пока все хорошо.

Итак, я создал репозиторий с именем TestRepository , который содержит следующее:

public class TestRepository : ITestRepository {
    public TestContext _context;
    public TestRepository(TestContext context) {
        _context = context;
    }

    public async Task IncrementCounter() {
        var row = await _context.Banks.FirstOrDefaultAsync();
        row.Counter += 1;
        await _context.SaveChangesAsync();
    }
}

Таким образом, он просто увеличивает значение в столбце на 1 - асинхронно .ITestRepository добавляется к службам IoC и внедряется там, где это необходимо.

Сценарий:

Пользователь A вызывает API, получает новый экземплярTestContext, и теперь вызывает метод IncrementCounter.

До SaveChangesAsync вызывается Пользователь B сделал вызов API, получил новый TestContext и имеетВызвал метод IncrementCounter.

Сейчас Пользователь A сохраняет значение 1 в столбце, в то время как Пользователь B также считает, что он должен сохранить значение 1 , но на самом деле это должно быть 2 .

Даже после прочтения соответствующей документации я все еще не уверен, как гарантировать, что метод IncrementCounter правильно увеличивается, даже если несколько пользователей вызывают API со своим собственным экземпляром TestContext .

Я мог бы запутать себя больше, чем следовало бы, но кто-то мог бы уточнить, как обрабатывается параллелизм между различными экземплярами одного и того же контекста?

Соответствующая документация:

Обновление 1

Я думал о реализации аннотации данных [TimeStamp] для нового свойства rowVersion, а затем перехватываю исключение DbConcurrencyException, как объясняется в соответствующей документации, норешает ли это проблему, что это DbContext в области видимости и, следовательно, другой контекст, который пытается SaveChangesAsync ()?

1 Ответ

0 голосов
/ 02 октября 2018

Это на самом деле не имеет ничего общего с продолжительностью жизни объекта, областью видимости или другим способом.Параллелизм - это параллелизм.Выполнение чего-то вроде увеличения счетчика по своей сути не является потокобезопасным, поэтому вы должны либо использовать блокировки, либо оптимистичный параллелизм, как и в любой другой поточно-небезопасной задаче.Блокировки базы данных обычно не одобряются, поэтому предпочтительным подходом, безусловно, является оптимистичный параллелизм.

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

Например, пользователь A и пользователь B пытаются сохранить значение 1 одновременно.Допустим, пользователь B получает за несколько миллисекунд раньше и успешно обновляет столбец.Затем он также обновляет столбец отметки времени.Когда приходит обновление пользователя А, обновление завершается неудачно, поскольку столбец отметки времени больше не совпадает.Это пузыри обратно в EF, где он бросает DbUpdateConcurrencyException.Ваш код перехватывает это исключение и отвечает повторным запросом строки, где столбец теперь равен 1, а не 0 (и получает последнюю метку времени), увеличивается до 2, а затем пытается сохранить снова.На этот раз другие пользователи ничего не делают, поэтому все проходит успешно.Тем не менее, вы также можете иметь другое исключение параллелизма.В этом случае вам необходимо промыть и повторить, в конечном итоге попытаться сохранить 3 в столбце и т. Д.

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

[Timestamp]
public byte[] Version { get; set; }

С этим (и после того, как вы явно перейдете), EF начнет генерировать исключения при сохранении, как описано выше, когдаесть конфликт.Чтобы поймать и обработать их, я бы предложил использовать такую ​​библиотеку, как Polly .Он позволяет настроить политики повторных попыток, что упрощает обработку этой повторяющейся логики сохранения-запроса-увеличения.

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