In my previous post I referred to the fact that the data-access code in CSLA is mangled in with the rest of the business logic. In this post I'd like to explain a bit on how I solved this.
The general code pattern makes use of a DataPortal which in turn invokes a method on the business object to retrieve the code. A sample if this is when you are going to load a set of entities from the database. You invoke a static method on your entity e.g. Customer.GetCustomer(customerId), this method calls DataPortal.Fetch<Customer>(new CustomerCriteria(customerId)). The dataportal class is a component that does a lot of magic with reflection, so I will omit the internals here. What DataPortal does is find a method DataPortal_Fetch with a CustomerCriteria parameter which returns a single customer. In this method you can invoke the data-access code to retrieve the customer. The whole sequence is displayed below.
Normally the DataPortal_Fetch method for Customer would look like this:
SqlConnection conn = new SqlConnection(Properties.Settings.Default.CustomerConnectionString); SqlCommand cmd = new SqlCommand("proc_CustomerInsert",conn); // Set some parameters using(SafeDataReader reader = new SafeDataReader(cmd.ExecuteReader())) { if(reader.Read()) { LoadObjectData(reader); } }
My solution involves some modifications in the DataPortal_Fetch method and the entity class (Customer). I extracted all the data-access code to a separate Provider class which allows me to execute various stored procedures in the database. It basically is a strong method-named way to access stored procedures. The DataPortal_Fetch method is modified so that it invokes a DataProvider which is accessible through a static property. The method now looks like this:
using(SafeDataReader reader = new SafeDataReader(DataProvider.GetCustomer(criteria.CustomerId)) { if(reader.Read()) { LoadObjectData(reader); } }
You could argue that I still use a datareader. I am doing this, because I don't want my data providers to know about my business objects. All they should do is give me some data when I invoke a method, this data should be provided to me as efficiently as possible. That's also why I don't use datasets, retrieving datasets is like maintaining state twice, CSLA already does this in the base classes, so I don't need to do that anymore.
The dataprovider is pretty straightforward, it implements the code (except for the using part) that was implemented by DataPortal_Fetch earlier.
I can also use some form of dependency injection here if I want to, because I'm using a static property DataProvider to get access to my dataprovider instance. I could modify the body of this property to do something like this:
public static ICustomerDataProvider DataProvider { get { if(_dataProvider == null) { IApplicationContext context = ContextRegistry.GetContext(); _dataProvider = (ICustomerDataProvider)context.GetObject("Customer.DataProvider"); } return _dataProvider; } }
IApplicationContext and ContextRegistry are two classes from the Spring.NET framework. I didn't bother to actually check if spring does singleton instantiation for me, this should however work as intended.
One of the things you can now do is configure a different provider in the configuration of the application. For example, you decide one day you want to synchronize the data in the database using the Microsoft Sync framework. The dataprovider is a good place to implement this kind of work. You will not have the richness of the business objects in that layer, it does save you a lot of work wading through the extra data that is not actually needed for synchronization.