У меня есть контроллер, который с действием, создающим группу, записывает в две разные таблицы базы данных. Как вы можете видеть ниже, я проверяю, существует ли группа, чтобы вы могли иметь только одну группу с уникальным именем, но я мог видеть, как этот код неверен, если два запроса приходят одновременно для одного и того же имени группы, но я чувствую, что это крайне низкая вероятность.
Вот мой код, и тогда у меня было три вопроса:
[HttpPost]
public async Task<ActionResult> Post(CreateGroupDto createGroupDto)
{
//See if this group exists yet (todo: move to action filter)
var existingGroup = _dbContext.Groups.SingleOrDefaultAsync(g => g.Name.ToLower() == createGroupDto.Name.ToLower());
//The group name isn't taken yet, so create this group
if (existingGroup == null)
{
var user = await _dbContext.Users.FindAsync(GetClaimsUserId());
var group = _mapper.Map<CreateGroupDto, Group>(createGroupDto);
//Add the Group LINE A
await _dbContext.Groups.AddAsync(group);
//Add the user as admin of this group LINE B
await _dbContext.UserGroups.AddAsync(new UserGroup { .. values set here ..} );
//Now that everything is added, save changes
//LINE C
await _dbContext.SaveChangesAsync();
return Created("/api/groups/", group.Id);
}
else
return BadRequest();
}
Хотя этот код работает для меня нормально, я бы хотел избежать вызова SaveChangesAsync (), если что-то пойдет не так и нужно просто обратиться к следующему:
Вопрос № 1
Если строка A запускается, то в ПАМЯТЬ dbContext добавляет новую группу
Если строка B запускается, то в ПАМЯТЬ dbContext добавляет новую группу пользователей
Когда запускается строка C, она сохраняет оба в базе данных, и если с любым из них возникает исключение, тогда откатывается ВСЕ транзакции (ничего не остается в частичном состоянии) и ошибка 500 возвращается к вызывающему API. Это как если бы никаких данных не было сохранено.
Вопрос № 2
Можно ли вызывать SaveChanges в самом конце, когда все настроено? Я мог видеть, как это контекстно - иногда я хочу сохранить, чтобы увидеть, сработало ли это, прежде чем добавлять что-то еще, а иногда я хочу сохранить, чтобы добавить все одновременно. Многие говорят, что DbContext - это UOW, поэтому в этом случае, если я сохраню транзакции в строке A, а затем снова сохраню в строке B, как мне откатиться?
Вопрос № 3
Я мог видеть, как было бы полезно обернуть все это в блок try..catch, однако мне интересно, если это необходимо - я мог бы добавить некоторый глобальный фильтр исключений, чтобы просто вернуть дружественное сообщение, но строго с точки зрения состояния транзакции, следующее точное (а если нет, есть ли у вас рекомендации действовать более адекватно / разумно):
Мне не нужно обязательно обрабатывать связанные с базой данных исключения в каждом отдельном действии каждого контроллера, потому что в конце концов ошибка приведет к тому, что DbContext автоматически откатит все (если я не вызываю SaveChanges несколько раз, так как упоминается в моем вопросе № 2).
Спасибо!