_context.SaveChanges () работает, но await _context.SaveChangesAsync () не работает

Я изо всех сил пытаюсь что-то понять. У меня есть веб-API .Net Core 2.2 с базой данных MySQL 8 и использую библиотеку Pomelo для подключения к серверу MySQL.

У меня есть метод действия PUT, который выглядит так:

// PUT: api/Persons/5
[HttpPut("{id}")]
public async Task<IActionResult> PutPerson([FromRoute] int id, Person person)
{
    if (id != person.Id)
    {
        return BadRequest();
    }

    _context.Entry(person).State = EntityState.Modified;

    try
    {
        _context.SaveChanges(); // Works
        // await _context.SaveChangesAsync(); // Doesn't work
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!PersonExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

Согласно моим комментариям в приведенном выше фрагменте кода, когда я вызываю _context.SaveChanges (), он работает (т.е. обновляет соответствующую запись в базе данных MySQL и возвращает 1), но когда я вызываю < em> await _context.SaveChangesAsync (), он не работает (не обновляет запись и возвращает 0). Он не генерирует исключение или что-то еще - он просто не обновляет запись.

Любые идеи?


person Fabricio Rodriguez    schedule 09.09.2019    source источник
comment
Здесь что-то еще происходит. SaveChanges буквально просто вызывает SaveChangesAsync и блокирует его, пока он не вернется. В EF Core нет настоящего неасинхронного доступа.   -  person Chris Pratt    schedule 09.09.2019
comment
Я полностью с вами согласен. Я не могу это объяснить! : / Может, это ошибка Помело?   -  person Fabricio Rodriguez    schedule 10.09.2019


Ответы (2)


Как я уже сказал в своем комментарии выше, в EF Core нет настоящих методов синхронизации. Методы синхронизации (например, SaveChanges) просто блокируют асинхронные методы (например, SaveChangesAsync). Таким образом, невозможно, чтобы SaveChanges работал, если SaveChangesAsync не работает, поскольку первый просто прокси-сервер для второго. Здесь есть еще одна проблема, которая не очевидна из предоставленного вами кода.

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

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

Короче говоря, вот как должен быть структурирован ваш метод:

// PUT: api/Persons/5
[HttpPut("{id}")]
public async Task<IActionResult> PutPerson([FromRoute] int id, PersonModel model)
{
     // not necessary if using `[ApiController]`
    if (!ModelState.IsValid)
        return BadRequest();

    var person = await _context.People.FindAsync(id);
    if (person == null)
        return NotFound();

    // map `model` onto `person`

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        // use an optimistic concurrency strategy from:
        // https://docs.microsoft.com/en-us/ef/core/saving/concurrency#resolving-concurrency-conflicts
    }

    return NoContent();
}

Я хотел, чтобы код был прямолинейным, но для обработки оптимистичного параллелизма я бы действительно рекомендовал использовать библиотеку обработки исключений Polly. Вы можете настроить политики повторных попыток, которые могут продолжать попытки обновления после исправления ошибок. В противном случае вам нужно будет try / catch внутри try / catch внутри try / catch и т. Д. Кроме того, DbUpdateConcurrencyException - это то, что вы всегда должны каким-то образом обрабатывать, поэтому повторный бросок не имеет смысла.

person Chris Pratt    schedule 10.09.2019
comment
Спасибо за очень подробный пост, Крис. Я ценю это и полностью с вами согласен. Я попробую это сделать и свяжусь с вами как можно скорее. - person Fabricio Rodriguez; 11.09.2019

Мне искренне жаль всех, кто потратил время на этот вопрос. Я выяснил проблему, и это была глупая ошибка, которую я сделал в своем dbContext. У меня есть настройка контрольного журнала, поэтому я отменяю SaveChangesAsync, OnBeforeSaveChanges и OnAfterSaveChanges. В этом коде была ошибка. Однако я не отменяю SaveChanges, поэтому это все еще работало. Извините!

person Fabricio Rodriguez    schedule 30.09.2019
comment
Спасибо, что поделились своим решением! - person lauxjpn; 06.12.2019