Part 3 - Creating Business Entities from Data Entities
In part one of this series we looked at how LINQ to SQL entities are structured by analyzing its members and mappings. We concluded that that they aren’t suitable in a model where we would derive from them because doing so would hide the LINQ to SQL mappings from the query provider. Creating a translation layer to handle loading and tracking of business entites would be too monumental a task to take on. Clearly that’s not an option either. Ideally, we want to encapsulate the data entities in business entities without sacrificing code re-usability. Fortunately, we have a neat pattern that we can use to do just that and it’s called object composition.
Do the names Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides ring a bell? Do you know what I’m referring to when I say, “GoF”? Yes, you guessed it. The Gang of Four and their book, Design Patterns: Elements of Reusable Object-Oriented Software. In the book, they cite several important design techniques such as programming to an interface and object composition. The one we’re interested in right now is object composition. Take for example a business requirement where every customer record must have address data for billing and shipping. Because the requirements for the Customer object only require a couple of addresses we can model the relationship with object composition using an Address class. The code sample below illustrates how we would write the Customer class using this pattern. Notice that there are two public Address properties which represent billing and shipping data as separate object instances.
With a little tweaking, we should be able to apply this pattern to reuse the LINQ to SQL entities within our business classes. In the following code sample, we start by writing a business layer customer class called CustomerBus with an internal constructor that accepts a LINQ to SQL entity reference. A private member that composes the LINQ to SQL Customer class keeps the reference in a safe place. Now here’s where we deviate a little from the composition pattern. Rather than adding a public property to expose the LINQ to SQL class, we’ll add public properties that expose each of the LINQ to SQL entity properties instead. Doing this gives us more granular control in dealing with the data entity properties. For example, we can change the names of the exposed properties, hide certain properties, and create custom domain specific validation logic for those properties we choose to expose.
Let’s take a look at how we would create relationships using this model. One advantage of keeping a reference to the LINQ to SQL entity is that we have access to the relationships in the data layer. Remember, our business entities can be constructed with the LINQ to SQL entities passed in as an argument to the internal constructor. We can use the existing relationships created by LINQ to SQL in the data layer for free. Our job just got a lot easier because all we need to worry about now is promoting the LINQ to SQL entity to a business entity. Subsequently, we’re going to need to expose the LINQ to SQL entity member by declaring the property as “internal”. This exposes the member to the other business entities in the assembly safely. We’ll need this so we can read the LINQ to SQL entity to customize the add and remove logic in our collections.
Simply exposing the EntitySet<T> member from the data layer is not desirable in this case because that would expose the LINQ to SQL entities. Support for customized add and remove logic to handle the EntitySet<T> reference requires a specialized collection class. Our new collection class derives from System.Collections.ObjectModel.Collection<T> which implements many of the behaviors we need to manage the contained entities. By deriving from Collection<T>, we get all of its functionality for free. Next we can create a custom constructor that accepts the EntitySet<T> reference and declare it internal to the assembly. We want to do this so that new collections of this type can only be instantiated from within this assembly to ensure that all instantiated objects are connected to a context. The constructor will be responsible for promoting the LINQ to SQL entities to business entities and adding them to the collection. Next we can add the specialized add and remove methods that hide the base class implementations to support management of the EntitySet<T> reference. Now adds and removes are tracked by a context and can be processed as a batch. Cool!
At this point, we can add the custom collection members to the business class as seen in the following code sample. The custom “lazy” getter implementation ensures that we load the collection once it is requested and keeps a reference to the instance for future reference. Now we have a working model that deals with the LINQ to SQL entities behind the scenes allowing for presentation code to interact purely with business entities. Additionally, we can implement business-specific logic in the business classes in the form of validations or custom behaviors.
Yes, I know it’s lot of code to have to write by hand. But this is one of those busy tasks that can clearly be remedied with a custom code generator. I’m currently working on one that I will make available in a future post. Until then, there will be a lot of code to write. The result of writing the code is clear. We’ve created a business entity layer that abstracts the data entities away and gives us a nice place to add domain logic. Another point of consternation might be with the pervasive use of the LINQ to SQL entities in the business layer. This is one case where I feel the benefit of having those entity references localized in each business class outweigh the challenges that might be introduced with changes in the data tier. We can somewhat isolate those issues with a custom code generator which should take most of the pains away. The code generator I’m working on declares each business class as a partial class. This will allow us to keep our custom implementation separate from anything that was automatically generated by the code generator.
In the next post, we’ll revisit the BusinessContext class to give it functionality to attach newly constructed business objects. We’ll also take a look at the result of our efforts so far with a sample web application that uses our new business layer and take a peek at SQL Profiler to check out how our changes impact LINQ to SQL’s query provider.