I am in the process of converting a large Classic ASP web application to ASP.Net MVC using an n-tier approach. In my DAL I use ADO.Net to query the database and transform the queries into objects. I also have a BLL for things like calculations and validation.
My question concerns performing calculations in the DAL when the calculations are needed in order to transform the query into objects. To give an example consider an invoicing system with summary info as well as line items:
public class Invoice
{
public int InvoiceID { get; set; }
public DateTime InvoiceDate { get; set; }
public decimal InvoiceTotal { get; set; }
public List<InvoiceLineItem> LineItemList { get; set; }
}
So my code to transform the line items in the database query would look like this:
decimal InvoiceTotal = 0;
var LineItem = new InvoiceLineItem();
while (Reader.Read())
{
LineItem.ItemID = Extensions.SafeGetInt(Reader, "ItemID");
LineItem.Price = Extensions.SafeGetDecimal(Reader, "Price");
LineItem.Quantity = Extensions.SafeGetInt(Reader, "Quantity");
LineItemList.Add(LineItem);
InvoiceTotal = InvoiceTotal + (LineItem.Price * LineItem.Quantity);
}
Invoice.InvoiceTotal = InvoiceTotal;
etc ...
So here's my question: Considering my n-tier architecture, is my DAL the right place to do the perform the InvoiceTotal calculation? Considering that part of the BBL's job is to perform calculations, does this violate separation of concerns between the DAL and the BLL? Or am I taking the BBL's function of performing calculations too literally and it is OK to do calculations in the DAL if those calculations are required to fill the model? One reason I find it appealing to do the InvoiceTotal calculation in the DAL is because I only have to iterate once over the invoice item records once. If I created a separate InvoiceTotal function elsewhere to get the InvoiceTotal then I would have to iterate over the records a second time.
Edit: It turns out that the real question is not whether calculations should be permissible in the DAL, but whether InvoiceTotal should be in my Model at all. From a database normalization perspective, it is not required because the totals can be calculated from line items. In that case InvoiceTotal should not be in my Model, but should be in my ViewModel, in which case there is no need to do calculations in my DAL. I could ignore database normalization concerns for performance reasons and include InvoiceTotal in my Model, but if that is the case, I would persist InvoiceTotal to the database in which case when filling my Model no calculations would be necessary as I would just pull the value from the Database.
Lesson Learned: If I am tempted to do calculations in my DAL, my model is likely flawed.
I would add the calculation to the business logic layer
The initial calculation of the invoice total should be available in the application before you have written it to the database so you don't want the only place that the total is calculated to be when retrieving records.
Another good reason to add it to the business logic layer is that it can be derived from the line data returned from the DAL which will keep the DAL layer focussed on writing and reading data. This also keeps your calculation in one place.
However since invoice totals are fixed once sent you may want to write the initial value on first save then allow manual amendment. In this case the invoice total calculation would be done in the business layer, but the value would also be written to and retrieved from the database with no recalculation.