17 November 2009

Business and Data Layers

What follows in this entry is a way that works well for me to architect a Business Logic Layer (BLL) and Data Access Layer (DAL) into my projects. This may not be the best way, so any constructive feedback is welcome. I love to learn new and more efficient ways of doing things. (Read: I'm lazy, and if I can make it easier on myself, I will.)

Naming

DAL

I tend to like to organize my DAL in a structure similar to the database itself. The namespace ends up being something like:

com.project.DAL.DbName.TableName.Entity.Select.All()

com.project.DAL is the namespace for the DAL project
DbName.TableName is a folder structure
Entity is a class
Select is a nested class
All is a static method
(compare the latter two parts to doing a static method called SelectAll under the Entity class)

If there are specialized procedures that don't map to specific tables, I might make a "Procedures" class under the database folder to encapsulate those.

BLL

The BLL class, I tend to name based on functionality rather than database organization. Something like:

com.project.BLL.CRM.Customer.Get.All()

Interfaces

DAL

The DAL only returns DataTables or DataSets. I don't even bother with typed data sets because of the added hassle of regenerating the XML files each time I make a change.

Returning DataTables has a number of advantages.

For one thing, it's an abstraction over the database. I could even take DataTables pulled from completely different servers, put them in the same DataSet, and use DataRelations to query them in memory to get exactly what I need.

For another thing, if no extra business logic is needed (perhaps the table is only for display, not for editing), then I can skip creation of C# objects at the business layer and bind the DataTable from the DAL directly to web controls. Out of the box, DataTables already support all the niceties of web controls like paging and sorting.

BLL

The business layer objects are Plain Old CLR (C# or VB .NET) Objects (POCOs). They look something like this:
    public class Customer : IInitable  // my interface to make an Init method
{
public int customer_id { get; set; }
public string name { get; set; }
public CustomerTypeEnum type { get; set; }
public DateTime since { get; set; }
public List<History> HistoryEntries { get; private set; }

// note absence of constructor... CLR makes a default constructor this way

#region IInitable Members
public void Init()
{
... // set the HistoryEntries with something like this.HistoryEntries = History.Get.ByCustId(this.customer_id);
}
#endregion

public static class Get // nested class to handle fetch operations
{
public static List<Customer> All()
{
DataTable customer_table = DAL.DbName.Customers.Select.All();
List<Customer> ret_val = Converter.Fill<Customer>(customer_table);
return ret_val;
}
}
}
By convention, I use the same field names from the database as my object property names. That makes converting the data from the database to a C# object easy with reflection. I use a method similar to what I blogged about a while back that does this for me. Something like this:
    public static void Fill(object obj, DataRow row)
{
// get object's writable properties into dictionary by name
...
// loop through each column of the datarow
...
// if the prop name is the same as the column name
// check/handle: null values, enum types, type conversion
// assign obj to property
}
When I need to convert a whole DataTable into a list of objects, I wrote another convenience method:
    public static List<T> Fill(DataTable Table) where T: IInitable, new()
{ // where part means that T implements IInitable interface as has a default constructor
List<T> ret_val = new List<T>();
foreach (DataRow row in Table.Rows)
{
T obj = new T();
Fill(obj, row);
obj.Init(); // IInitable defines this method in case the object needs initialization after filling.
ret_val.Add(obj);
}
return ret_val;
}
IInitable is just a one-method interface that I wrote that makes the C# BLL class implement an Init method that returns void. Because I have to use empty constructors for uniformity, this Init method allows me to still initialize parts that would normally be initialized in the constructor, but after the data is filled in.

The CUD part of CRUD operations, at the BLL layer can take in an object and then use that object's properties for parameters of the DAL method.

However, to be really lazy, I am considering making an overload method for my DAL operations so that the BLL can just pass in the object directly and the overload method will use reflection to pull the properties out of the object and pass them into the method with typed parameters. I can probably encapsulate this with a separate static converter method so that the overload will essentially be a one-line call to the converter method (and therefore not much coding overhead), which invokes the real method. Once I get around to trying this, I'll post something about it.

No comments: