1

I have Business logic layer and DB layer (Entity framework). For example, I receive some data from DB.

DB layer:

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 layer (it's the simplest example, sense of BL layer - just "throw" result from DB to client (ASP.NET MVC in my case, but no matter). Of course, BL method can have any additional logic):

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

It works and works fine. But I want to throw my own exception if record does not exists (i.e. record was deleted, but user has link to his bookmarks):

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

I have 2 way to throw it: 1. In DB layer:

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;
}

or in the BL layer:

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

and then catch this exception on client side (controller of ASP.NET MVC for example) and handle it by appropriate way. Both approach will work, but where is more logical place to throw such exception?

EDIT: Also, there is a difficult to throw this exception in BL when I want to edit/delete record. I.e. I have code:

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();
}

If I not throw exception in DB layer I get common exception type (I assume, NullReferenceException) in BL. Maybe, it's enough? Any other situations, when I can get NullReferenceException?

Oleg Sh
  • 8,496
  • 17
  • 89
  • 159

1 Answers1

3

I would put the exception in your Business Logic error. The Database Layer should simply report what it returns; nothing in this case. This is only a problem because your business logic requires that it be. So, throw the error there rather than at a lower level.

Necoras
  • 6,743
  • 3
  • 24
  • 45
  • but it denies you to make 2 or more requests in one method of DB layer (because result of the first request can be null) – Oleg Sh Feb 18 '16 at 17:25
  • 1
    I try to make my DB layer as thin as possible. If your UI or workflow needs to make 2 database calls, it should be clear from the DB side that it's making multiple trips to the database. Abstracting that down to the database layer hides potential performance problems. Your BL layer could make multiple distinct DB calls as a single logical call, but since that's a logical call, it should be in the BL layer. – Necoras Feb 18 '16 at 17:36
  • but if I want to use transaction? I.e. remove record from one table and add to second... – Oleg Sh Feb 18 '16 at 18:46
  • 1
    That's a more complex operation. It might be better served by moving the whole thing into the database as a stored procedure, and then still only making a single call in the DB layer. The DB layer would then return a success or failure condition. If that fail condition breaks your workflow, then your BL can throw an exception. However, if you expect the possibility of a failure, I'd try code a response to that (notify a user, skip that record and continue processing, etc.), rather than simply throwing an exception and giving up. – Necoras Feb 18 '16 at 19:32
  • but I want to throw Exception and then response to that (notify a user etc) :) – Oleg Sh Feb 18 '16 at 20:12
  • look at my question. I have added something. Problem can be arise when you try add/edit record. Then it's impossible to throw my own exception in BL because exception (NullReferenceException) will be thrown before it, in DB layer – Oleg Sh Feb 18 '16 at 20:15
  • As I said above, if you want transaction level logic (which seems to be the case with your AcceptOrDeclineFileAsync method) then move all of that logic into a single stored proc that can succeed or fail. If it fails, you can return whatever message you'd like from the stored proc. Your Business Logic layer can then handle that as appropriate. If you don't need a transaction, then split out the functionality into discrete get and delete functions on your DB layer, and call them as appropriate in your BL layer. – Necoras Feb 18 '16 at 20:35
  • in that case we don't have 2 operations. Really, there is one operation, edit entity. We get it, change some fields and save. But we can try to get null object (pass invalid ID). So, it's argument to throw this own exception in DB layer – Oleg Sh Feb 18 '16 at 21:29
  • I see. That should be a BL level function which relies on individual db level functions. So AcceptOrDeclineFileAsync() calls db.GetElement. If that returns an Element, continue with your modification and then call db.SaveElement. If db.GetElement returns null, then AcceptOrDeclineFileAsync() can throw an exception. That keeps your DB layer thin (all it does is get and save), while keeping the actual logic in your BL layer. – Necoras Feb 18 '16 at 22:12
  • AcceptOrDeclineFileAsync() function is DB layer too. No matter, it calls one more method (which returns element or null) or my variant. Exception will be thrown by DB layer in any case :) – Oleg Sh Feb 18 '16 at 22:32
  • AcceptOrDeclineFileAsync() *should not* be a database layer function. It's performing business logic, not database logic. Additionally, when you're updating objects in your database, you should accept full objects and update the whole object, not individual bits and pieces. AcceptOrDeclineFileAsync() shouldn't accept 3 parameters, it should accept just a file (or Element as you've called it). Consider, what happens if you need to modify 50 pieces of your Element? Would you create a method with 50 parameters? – Necoras Feb 18 '16 at 22:55
  • if I need to modify 50 pieces of my Element I would create special class and pass object of this class to method. Element can't be passed to DB layer because Element class is DB layer class and BL don't know anything about this class. DB layer should be depended from BL, not vice versa – Oleg Sh Feb 18 '16 at 23:31
  • why AcceptOrDeclineFileAsync can't be DB layer method? I have similar method in BL too, but with business logic operations. My DB method just does operations with DB, nothing more – Oleg Sh Feb 18 '16 at 23:32