This is the third blog in my series describing the new DAL 2 data layer that we are introducing in DNN 7. In the first I gave an introduction into why we were introducing a new data Layer, and some of the basic features it provides, and in the second article I introduced the new built in IRepository of T implementation.
The simple repository introduced in the previous blog is great for simple modules. However, in the previous blog we had to make some changes to our TaskInfo class and Tasks table to make everything work using PetaPoco’s mapping conventions. However, PetaPoco supports an IMapper interface and, in the DAL 2, we have implemented a custom IMapper implementation (PetaPocoMapper) that provides support for three Attributes which are defined in the DotNetNuke.ComponentModel.DataAnnotations namespace. For those of you who have used Entity Framework or LINQ 2 Sql these are similar to the attributes used in those frameworks.
- TableName - As mentioned in the previous blog, PetaPoco’s default mapping convention for table name is to map classes to a table that is the plural of the class’s name. The TableName Attribute allows us to specify the actual Table Name as a class level Attribute.
- ColumnName - PetaPoco’s default mapping maps object properties to columns which have the same name. The ColumnName Attribute allows us to define a column name that the property maps to.
- PrimaryKey - We can define the Primary Key column for our table as a class level Attribute.
Use of these attributes is demonstrated in Listing 1, where we show the original TaskInfo object decorated with custom mapping attributes to map to the Tasks table. This ability to map the properties to columns means that we can be flexible with our naming.
Listing 1: The TaskInfo Model
The TaskInfo class should now map correctly to the Tasks table from the previous blog - Listing 2.
Listing 2: The Create Table SQL to create the Tasks Table
The DAL 2 PetaPocoMapper class also takes care of the table prefix or object qualifier. (Note: There is a bug in the CTP which means that object qualifiers don’t actually work - this bug will be fixed in the next pre-Release package of DNN 7.).
It can often be unnecessary and lead to poor performance to go to the database every time we need a collection of tasks or even a single task. In the DotNetNuke core we make extensive use of in-memory cache’s to deliver the best performance possible.
Usually items are cached in a collection and if individual items are required they are retrieved using a LINQ query on the in-memory collection. The DAL 2 provides a very simple caching mechanism, through the use of a fourth Attribute. The Cacheable attribute allows the developer to define the cache key, the priority and the timeout, as shown in Listing 3.
Listing 3: Adding the Cacheable Attribute
Under the covers the DAL 2 calls DotNetNuke’s built in caching support, so all the normal rules on object caching are applied. For example, caching is disabled if the Host Setting is set to None. But for module developers, all you need to do to add support for caching is to add the cacheable attribute.
Most of the methods of the repository class are aware of the attribute and do the appropriate thing: Get methods check the cache if caching is enabled and update/insert/delete methods clear the cache when called - the exceptions being the methods which take a "SqlCondition” parameter. This can be seen by looking at the code in Listings 4 and 5.
Listing 4: RepositoryBase’s Get method
Listing 5: The RepositoryBase’s Delete method
Ignoring the IsScoped property for now, the Get method checks if the class T is cacheable. If it is cacheable it calls DotNetNuke’s GetCachedData utility method, which takes a delegate as a callback to use if the cache has expired - in this case GetInternal(). If T is not cacheable the GetInternal() is called directly. GetInternal is implemented in the PetaPocoRepository class and is shown in Listing 6.
Listing 6: PetaPocoRepository’s GetInternal method
So now we have the ability to modify our custom mappings through attributes and the ability to control caching, but we still are working with all the data in our module, rather than just the data for one specific instance - or for some modules for one specific portal.
To solve this problem the DAL 2 provides a Scope attribute. The Scope attribute identifies a property of the object which is used to scope the data. In most cases this would be the module Id but by keeping this generic we can support different types of scoping - portal Id or user Id as an example.
Lets look at our TaskInfo class after adding the Scope attribute (as well as a ModuleID property) - Listing 7.
Listing 7: The TaskInfo class updated to scope by ModuleID
We also have to update our Table as well so it has a ModuleID column - LIsting 8.
Listing 8: Updated Create Table SQL to include the ModuleID
And finally we have to make a couple of minor changes to the TaskController class - Listing 9.
Listing 9: Updated TaskController methods to handle the ModuleId Scope
Notice that the Repository provides overloads for the Get and GetById method which are used to pass the scope value - in this case the moduleId.
Under the covers the Repository knows that the scope value “moduleId” refers to the Scope column “ModuleID” and generates the appropriate WHERE clause, to query the database. The cool thing is that this works with the cacheable attribute, so that if the class is both scoped and cacheable then there are separate cached collections for each scope value.