SignalR Core + EF Core, как правильно обработать ошикби базы?
Допустим, что есть Real-time приложение, которое обращается к базе для CRUD операция из хаба, но в какой-то момент база падает, либо случается какая-то иная ошибка, как правильно осуществлять CRUD операции, чтобы на клиенте не сыпались Exception'ы, что "На сервере что-то не так"?
Оборачивать каждое обращение к базе в try-catch? Слишком напряжно, ведь методов хабов, как и самих хабов, может быть много, одинакового кода тоже будет много.
Пока придумал такой вариант:
- Общий класс, для отдачи ответов от методов хаба:
public class UResponse<T>
{
/// <summary>
/// Код ответа сервера.
/// </summary>
public UResponseCode ResponseCode { get; }
/// <summary>
/// Результат запроса.
/// </summary>
public T Response { get; set; }
/// <summary>
/// Конструктор.
/// </summary>
public UResponse(UResponseCode responseCode, T response)
{
ResponseCode = responseCode;
Response = response;
}
public static implicit operator bool(UResponse response) => response?.ResponseCode == UResponseCode.Ok;
}
public enum UResponseCode
{
/// <summary>
/// Всё ок.
/// </summary>
Ok,
/// <summary>
/// Ошибка сервера.
/// </summary>
ServerError,
/// <summary>
/// Ошибка базы данных.
/// </summary>
DbError
}
- Метод-расширение для DbContext'a:
public static async Task<UResponse<TResponse>> ContextSafeFunc<TResponse>(this DbContext context, Func<Task<TResponse>> func, TResponse defaultValue = default)
{
try
{
var res = await func().ConfigureAwait(false);
return new UResponse<TResponse>(res, UResponseCode.Ok);
}
catch
{
return new UResponse<TResponse>(defaultValue, UResponseCode.DbError);
}
}
- Работа с базой данных через данное расширение в методе хаба:
public Task<UResponse<IEnumerable<GetOrderDto>>> GetOrdersAsync(int offset)
{
return context.ContextSafeFunc(async () =>
{
var res = await context.Orders.AsNoTracking()
.OrderByDescending(x => x.Id)
.Skip(offset)
.Take(20)
.ToListAsync()
.ConfigureAwait(false);
return mapper.Map<IEnumerable<GetOrderDto>>(res);
});
}
На сколько данный метод является костылем? Как его улучшить, чтобы при возникшей ошибке на клиенте ничего не ломалось, не вызывались Exception'ы, а только отдавался унифицированный ответ с кодами ошибок? (Т.е. при успешном запросе к базе данных ответ был => UResponseCode = UResponseCode.Ok, Response = результат запроса к базе; при неудачном запросе ответ был => UResponseCode = UResponseCode.DbError, Response = defaultValue (обычно default(T)))