Нет записи - место для исключения

У меня есть уровень бизнес-логики и уровень БД (инфраструктура Entity). Например, я получаю некоторые данные из БД.

Уровень БД:

public SmartphonePhotographerResponseManage ResponseManage(int RequestID)
{
    SmartphonePhotographerResponseManage response = (from i in db.SmartphonePhotographerResponses
                                                     where i.RequestID == RequestID
                                                     select new SmartphonePhotographerResponseManage()
                                                     {
                                                         ResponseID = i.ID,
                                                         FormattedAddress = i.EditorialPixlocateRequest.FormattedAddress
                                                     }).FirstOrDefault();
    return response;
}

Слой BL (самый простой пример, смысл слоя BL - просто "кинуть" результат из БД клиенту (в моем случае ASP.NET MVC, но не важно). Конечно, метод BL может иметь любую дополнительную логику):

    public SmartphonePhotographerResponseManage ResponseManage(int RequestID)
    {
        return _repository.ResponseManage(RequestID);
    }

Он работает и работает нормально. Но я хочу создать собственное исключение, если запись не существует (т.е. запись была удалена, но у пользователя есть ссылка на его закладки):

public class RecordNotFoundException<T> : Exception
{
    public RecordNotFoundException(T ID) : base(String.Format("No Records for passed ID={0}", ID.ToString()))
    {
    }
}

У меня есть 2 способа его бросить: 1. На уровне БД:

public SmartphonePhotographerResponseManage ResponseManage(int RequestID)
{
    SmartphonePhotographerResponseManage response = (from i in db.SmartphonePhotographerResponses
                                                     where i.RequestID == RequestID
                                                     select new SmartphonePhotographerResponseManage()
                                                     {
                                                         ResponseID = i.ID,
                                                         FormattedAddress = i.EditorialPixlocateRequest.FormattedAddress
                                                     }).FirstOrDefault();
    if (response == null)
        throw new RecordNotFoundException<int>(RequestID);
    return response;
}

или в слое BL:

public SmartphonePhotographerResponseManage ResponseManage(int RequestID)
{
    var response = _repository.ResponseManage(RequestID);
    if (response == null)
        throw new RecordNotFoundException<int>(RequestID);
    return response;
}

а затем поймать это исключение на стороне клиента (например, контроллер ASP.NET MVC) и обработать его соответствующим образом. Оба подхода будут работать, но где логичнее генерировать такое исключение?

РЕДАКТИРОВАТЬ: Кроме того, в BL сложно создать это исключение, когда я хочу отредактировать/удалить запись. т.е. У меня есть код:

public async Task AcceptOrDeclineFileAsync(int ElementID, bool accept, string smsSid)
{
    var element = (from i in db.SmartphonePhotographerResponseElements where i.ID == ElementID select i).FirstOrDefault();
    if (element == null)
        throw new CommonLibrary.RecordNotFoundException<int>(ElementID);

    element.ApprovedByEditorial = accept;
    element.SmsSID = smsSid;
    await db.SaveChangesAsync();
}

Если я не выбрасываю исключение на уровне БД, я получаю общий тип исключения (я полагаю, NullReferenceException) в BL. Может, хватит? Любые другие ситуации, когда я могу получить NullReferenceException?


person Oleg Sh    schedule 18.02.2016    source источник


Ответы (1)


Я бы поместил исключение в вашу ошибку бизнес-логики. Уровень базы данных должен просто сообщать о том, что он возвращает; ничего в этом случае. Это проблема только потому, что этого требует ваша бизнес-логика. Итак, сбрасывайте ошибку туда, а не на более низкий уровень.

person Necoras    schedule 18.02.2016
comment
но он запрещает вам делать 2 или более запросов в одном методе уровня БД (поскольку результат первого запроса может быть нулевым) - person Oleg Sh; 18.02.2016
comment
Я стараюсь сделать свой слой БД как можно тоньше. Если вашему пользовательскому интерфейсу или рабочему процессу необходимо сделать 2 вызова базы данных, со стороны БД должно быть ясно, что он совершает несколько обращений к базе данных. Абстрагирование этого до уровня базы данных скрывает потенциальные проблемы с производительностью. Ваш уровень BL может выполнять несколько отдельных вызовов БД как один логический вызов, но, поскольку это логический вызов, он должен находиться на уровне BL. - person Necoras; 18.02.2016
comment
но если я хочу использовать транзакцию? т.е. удалить запись из одной таблицы и добавить во вторую... - person Oleg Sh; 18.02.2016
comment
Это более сложная операция. Это может быть лучше, если переместить все это в базу данных как хранимую процедуру, а затем сделать только один вызов на уровне БД. Затем уровень БД вернет условие успеха или отказа. Если это условие сбоя нарушает ваш рабочий процесс, ваш BL может вызвать исключение. Однако, если вы ожидаете возможность сбоя, я бы попробовал кодировать ответ на это (уведомить пользователя, пропустить эту запись и продолжить обработку и т. д.), а не просто выдать исключение и сдаться. - person Necoras; 18.02.2016
comment
но я хочу сгенерировать исключение, а затем ответить на него (уведомить пользователя и т. д.) :) - person Oleg Sh; 18.02.2016
comment
посмотрите на мой вопрос. Я добавил кое-что. Проблема может возникнуть при попытке добавить/отредактировать запись. Тогда невозможно сгенерировать мое собственное исключение в BL, потому что исключение (NullReferenceException) будет сгенерировано перед ним на уровне БД - person Oleg Sh; 18.02.2016
comment
Как я сказал выше, если вам нужна логика уровня транзакции (что, похоже, имеет место в вашем методе AcceptOrDeclineFileAsync), переместите всю эту логику в один хранимый процесс, который может завершиться успешно или неудачно. Если это не удается, вы можете вернуть любое сообщение из сохраненного процесса. Затем ваш уровень бизнес-логики может обрабатывать это соответствующим образом. Если вам не нужна транзакция, разделите функциональность на отдельные функции получения и удаления на уровне БД и вызовите их соответствующим образом на уровне BL. - person Necoras; 18.02.2016
comment
в этом случае у нас нет 2 операций. Действительно, есть одна операция, редактировать сущность. Получаем, меняем некоторые поля и сохраняем. Но мы можем попытаться получить нулевой объект (передать неверный идентификатор). Таким образом, это аргумент для создания этого собственного исключения на уровне БД. - person Oleg Sh; 19.02.2016
comment
Я понимаю. Это должна быть функция уровня BL, которая опирается на отдельные функции уровня db. Поэтому AcceptOrDeclineFileAsync() вызывает db.GetElement. Если это возвращает элемент, продолжите модификацию и затем вызовите db.SaveElement. Если db.GetElement возвращает значение null, то AcceptOrDeclineFileAsync() может вызвать исключение. Это делает ваш уровень БД тонким (все, что он делает, это получает и сохраняет), сохраняя фактическую логику на вашем уровне BL. - person Necoras; 19.02.2016
comment
Функция AcceptOrDeclineFileAsync() также является уровнем БД. Неважно, он вызывает еще один метод (который возвращает элемент или ноль) или мой вариант. Исключение будет выброшено уровнем БД в любом случае :) - person Oleg Sh; 19.02.2016
comment
AcceptOrDeclineFileAsync() не должна быть функцией уровня базы данных. Он выполняет бизнес-логику, а не логику базы данных. Кроме того, когда вы обновляете объекты в своей базе данных, вы должны принимать полные объекты и обновлять весь объект, а не отдельные фрагменты. AcceptOrDeclineFileAsync() не должен принимать 3 параметра, он должен принимать только файл (или элемент, как вы его назвали). Подумайте, что произойдет, если вам нужно изменить 50 элементов вашего элемента? Вы бы создали метод с 50 параметрами? - person Necoras; 19.02.2016
comment
если мне нужно изменить 50 частей моего элемента, я бы создал специальный класс и передал объект этого класса в метод. Элемент не может быть передан на уровень БД, потому что класс Element является классом уровня БД, а BL ничего не знает об этом классе. Уровень БД должен зависеть от BL, а не наоборот - person Oleg Sh; 19.02.2016
comment
почему AcceptOrDeclineFileAsync не может быть методом уровня БД? У меня есть аналогичный метод и в BL, но с операциями бизнес-логики. Мой метод БД просто выполняет операции с БД, не более того - person Oleg Sh; 19.02.2016