Essence of reporting
Creating reports is usually frowned upon by developers. Mostly with good reasons. Our pattern recognition software detects that we are breaking the DRY principle. So we usually end up creating factory for creating reports. But then we notice that we need factory for creating factory for creating reports. So our little reporting engine breaks down under pressure and we end up using some enterprisey AbstractFactoryFactory tool. In spirit with true enterprisey values, we design our report in XML.
But wait. All we wanted to do is push some data to another presentation layer, namely Word/Excel/PDF. Let's stop and look at the patterns we are using to design our presentation view.
First let's look at simple report example. We need export list of our customers to Excel. We recognize that this is actually exporting list to grid like structure. We can then use this all around our application for exporting grids to it. Sound's like a perfect example of export to Excel/CSV.
Let's get back to reality. Our customer code looks something like:
class Customer
{
public string Name;
public string Address;
public decimal Balance;
}
var customers = List<Customer>();
If we squint and look at it, it seems that one row is our context, which maps to Customer
class. We need to duplicate this context for each item in collection customers
.
It would be great if we didn't need to code font, row and table properties in our code. After-all, our Office tools have come a long way, and we should separate presentation from model.
Let's look at more complex example now. We need to create invoice in Word. We recognize that what we really need to do is build a report which will display our data in page like structure - with support for headers, footers and sometimes continuation on second or even third page. Experience also tells us that next feature request we will get is to create multiple invoices in Word, but let's not get too far ahead of ourselves. If we squint and look at this report requirement we can recognize a couple of patterns:
- fields which are used mostly once and sometimes repeated on couple of places for readability. For example: date, address, invoice-number, etc.
- main collection which will be expanded based on number of items in it. That collection is pattern which we saw in our previous report, so we'll use table row as it's context again.
- occasional collection which follows the same pattern as the main collection but is used for less important data.
What we actually want to do is design just the bar bone layout of this document in Word. Add tags which we provide through our model and let designer do the prettifying. Our code would look something like:
class Invoice
{
public string Number;
public Customer Customer;
public DateTime Date;
public List<LineItem> Items;
}
class LineItem
{
public string Name;
public decimal Cost;
public int Quantity;
}
var invoice = new Invoice();
We've reused our Customer
class in our model.
After we've built report this way using our Office tool, we feel good about ourselves because we can redirect change requests to our designer as long as our model doesn't change.
What we want to do now is build the same report which can have multiple invoices in it. We recognize that we've just moved another layer above, and that our data which we currently provided to report is just and item in collection which we now want to provide. In code this would look like:
var invoices = new List<Invoice>();
This means that new context for each item is the whole template document we've designed and each item has smaller contexts (like tables for line items) which are available as collections in our model.
What we've extracted for now is:
- we can map objects to it's context.
- we want to replicate context's.
- we can have nested context's.
- our nested context can map to object item inside collection.