Aspects for Data
Data access is so important and difficult that it seems every generation of software development technologies must invent its own solution. Modern data access frameworks typically address two issues: persistence of objects (object-relational mapping) and transaction handling.
In essence, object-relational mapping (ORM) frameworks attempt to enhance CLR objects with new behaviors: lazy loading of properties, dirty flag management, data binding, or validation. Being knowledgeable of PostSharp, you must have immediately recognized use cases for aspects. However, most of these frameworks use two poor man's alternatives of AOP: C# code generation (which makes sense when, but only when, code is generated from a model or from the database), or overriding of data objects using JIT-emitted code (which has a lot of limitations).
We're not arguing that you should create your own ORM solution (although it sometimes makes sense), but if you're attacking a larger project, it may pay off to consider using aspects to bind your plain old domain objects to the ORM framework of your choice.
In this section:
PostSharp Partners for Data
Don't reinvent the wheel. Our partners deliver state-of-the-art persistence data based on PostSharp. Check their offering.
DataObjects.NET 4.0
DataObjects.Net v4.0 combines comprehensive business logic layer development framework, object-relational mapper (ORM) and a set of persistent storage implementations enabling the same BLL code work everywhere by the same way dramatically reducing the resources you need to develop an application dealing with persistent data. read more
Persistence
Persistence frameworks typically need to address the following issues:
- Field virtualization – A field is two things: semantics (read, write), and a location in memory where the value is actually stored (properties, however, are only the first thing: semantics). Sometimes, you want the semantics of a field but want to change the storage into something else. For instance, you may want the values of all fields to be stored in an hashtable; indeed, this approach makes it convenient to implement data transfer operations (such as SELECT, INSERT, UPDATE).
- Lazy loading – When representing large graphs of interconnected entities as CLR objects, you easily end up retrieving the whole database into memory when you actually needed a single entity. A naive algorithm would create objects not only for the required entity, but also for every entity it refers. This is nothing new and the answer is well-known: references must only be loaded on demand. Here also, intercepting all read accesses to fields or properties may help.
- Change tracking – Typically, a persistence framework does not update the back-end whenever
a field is changed; instead, it waits until a
SaveChangescommand is issued. At this moment, the persistence framework updates the back-end for all dirty objects in the current transaction. How to determine whether an object is dirty? You guessed it: we can use an aspect to intercept all write operations.
All the requirements above may be implemented using a LocationInterceptionAspect,
which lets you replace the implementation of read and write operations by wathever you want.
Transaction Handling
Back in the old (good?) days of COM+, transaction handling was considered so important that
it was implemented by the COM+ container; COM+ services (ServicedComponent in .NET) only had to specify their requirements
for transaction handling declaratively (using TransactionAttribute in .NET).
Since then, Windows Component Services have been replaced by lighter technologies (web services, WCF),
and the .NET Framework offers the namespace System.Transactions for lightweight transaction
handling.
Yet, the .NET Framework still does not offer declarative transaction handling; it's not that the requirement has suddently disappeared, but rather that the .NET, as opposed to COM+, do not allow the platform to add behaviors to user code.
Thanks to aspect-oriented programming, we have the best of both worlds: declarative transaction handling (as well as monitoring and other aspects) from the COM+ world and lightweightness from .NET.
The following code shows you how to encapsulate the functionality of TransactionScope
as a custom attribute:
/// <summary> /// Aspect that, when applied on a method, encloses it in a <see cref="TransactionScope"/>, /// and marks the <see cref="TransactionScope"/> as failed when the method results /// in an exception. /// </summary> [Serializable] public sealed class TransactionScopeAttribute : OnMethodBoundaryAspect { private readonly TransactionScopeOption option; public TransactionScopeAttribute() { } public TransactionScopeAttribute(TransactionScopeOption option) { this.option = option; } /// <summary> /// Method executed upon entry of the method to which this aspect is applied. /// </summary> /// <param name="args">Information about the method being executed.</param> public override void OnEntry(MethodExecutionArgs args) { args.MethodExecutionTag = new TransactionScope(this.option); } /// <summary> /// Method executed upon exit (success or failure) of the method to which /// this aspect is applied. /// </summary> /// <param name="args">Information about the method being executed.</param> public override void OnExit(MethodExecutionArgs args) { ((TransactionScope)args.MethodExecutionTag).Dispose(); } /// <summary> /// Method executed upon failure of the method to which this aspect is applied. /// </summary> /// <param name="args">Information about the method being executed.</param> public override void OnSuccess(MethodExecutionArgs args) { ((TransactionScope)args.MethodExecutionTag).Complete(); } }
This aspect ensures you will never forget to invoke the method TransactionScope.Complete again.
More importantly, it makes the transactional behavior of methods visible so System.Reflection –
for instance, you could generate documentation based on this aspect.
Summary
There are major benefits to using aspects inside your object persistence layer. The benefits are so important that you may want to design your ORM framework specifically for PostSharp, as X-tensive did with Data Objects .NET 4.0.
