Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Web-Application Framework - Catharsis - Part VIII - Data layer

2.88/5 (4 votes)
4 Oct 2008CPOL8 min read 1  
Catharsis web-app framework - Data layer

Introduction 

This article is the next step in Catharsis documented tutorial.  Catharsis is a web-application framework gathering best-practices, using ASP.NET MVC (preview 5), NHibernate 2.0. All needed source code you can find here.

Catharsis-title.png

No.ChapterNo.ChapterNo.Chapter

New solution VI CatharsisArchitectureXI Controller layer

II Home page observationVII Entity layer XII (UI Web layer - undone) 

III Access rights,
Navigation
VIII Data layer XIII Tracking, PAX design pattern

IV Localization, translationsIX Business layerXIV Catharsis Dependency Injection (DI, IoC) 

Enter into Catharsis, new EntityModel layer XV (Code Lists - undone)    

Data Layer

At this chapter we'll uncover the data project which is the only-responsible layer for persisting. There is no need to use database or even NHibernate for your application. You can even use more storage types, side by side. Let’s say that users and roles can be stored in XML, localized values can be shared in one database for more applications, and NHibernate could handle only business entities.

The variety is unlimited because of separation of concern. Only Data tier can persist and access data from storage. Every upper layer does not have to care about persistence and that’s the win.

Next paragraph will describe the Catharsis data layer implementation. Always is good to know how things are working. But keep in mind, that in your application you'll only:

  1. Use Guidance to generate files
  2. Concern on implementing business requirements

Nothing else.

Use Guidance!

As in the previous (Entity layer) chapter there is one big suggestion: use the Catharsis.Guidance. It’s ready to help you to create whole Entity infrastructure, or – as we'll see below – to create objects needed for one layer.

We'll continue with the previous example: There is entity Person, stored in namespace People. It has property Code (business unique key) and Second and First name. We've already created the Person.cs and PersonSearch.cs files and classes. They are plain (almost without functionality) and they need to be stored.

As storage serves Microsoft SQL Server database; table Person (see Chapter V - Enter into Catharsis, adding new Entity). 

As the DAO (data access object) we will use a little bit customized NHibernate ISession. The core of NHibernateDao class comes from  Billy McCafferty SharpArchitecture 0.7.3 and you should examine it for details (there is a lot of documented and described stories to get you quickly in, if you are interested in the low level design) 

Session per request design pattern

To smoothly work with NHibernate you need not only the ISession object but the storage for its reference as well. It must be stored for all the time you are directly accessing the storage. And due to the lazy-mode and the web based solution, the perfect and right place is the request Items object.

Request is exactly the scope in which you are accessing storage. It means changing entities, store changes and display all needed (lazily loaded) properties on UI. 

There are already implemented objects in Catahrsis.Mvc.dll, which will correctly create and delete ISession object and store the reference in the request Items object. These are: NHibernateSessionStarter, WebSessionStorage and CatharsisHttpModul

Starting sequence is placed in Global.asax (you can trace other calls in a source code…)

C#
public class GlobalApplication : HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        NHibernateSessionStarter.Execute();           
        // .... 

For closing NHibernate ISessions there is CatharsisHttpModule which is observing the EndRequest event. It is crucial when you are redirecting among controllers, what in fact ends with new request for every controller! The unclosed ISession disallows NHibernate to proceed and throws exception.

C#
protected virtual void EndRequest(object sender, EventArgs e)
{
    var session = HttpContext.Current.Items["nhibernate.current_session"] as ISession;
    if (session != null)
    {
        session.Close();
    }
}

All that stuff is simply doing needed job for you. You can or should observe them. Once you are familiar with them, forget them. They will serve you (your application) and do not need your attention anymore.

Catharsis Data layer 

Data layer is by default NHibernate based. There are generic DAO objects providing all needed CRUD (create, read, update, delete) methods for your Entity. In Catharsis.Data.dll you can find out NHGenericDao<T> with its partial extension for Tracked objects. In the ‘Firm.Product.Data.dll’ exists base object for your project DaoBase<T>.

Catharsis is obsessed with inheritance and encapsulation (maybe me…). Therefore, instead of using Utils.cs files and static methods, better way is put all these commonly reusable methods into DaoBase<T>. The good starting example is method GetListByCriteria() which can help you with basic processing of the EntitySearch object.

Paging

Paging is one of fancy things we could be proud of (but possible only due to NHibernate). Every Entity you are accessing has default Action ‘List’. It means, that even if there is a 100 thousand of rows in DB you are firstly navigated to List instead of Search (of course you can change it).

By default user gets 20 rows (you can change it in a SearchObject settings). Paging will throw current set of 20 rows and load new one and so on!

But even more, you can resort the list while you are standing on let’s say on 6th page.  The list will be resorted but you'll be still watching the 6th page ...

And that's all stuff is built-in, every entity gains the sortable list and paging when firstly comes to the light of ... your solution. Please examine it, you can adjust almost anything. The main stuff could be found in just mentioned parent method GetListByCriteria().

Three files for data layer

Guidance will generate three files; two will be placed in Data.dll and one into Common.dll. Just right click on a Data project and the Guidance menu item will appear (clicking on a folder will be awarded by prefilled namespace). Quick resume is shown on the picture.

DataGuidance.png

IEntityDao.cs

Every upper layer (it should be the business anyway) will never access the data.dll directly. As said in other chapters, there even does not have to be reference from business.dll to data.dll. The trick (good design) comes from using interfaces (DI, IoC see Chapter XIV - Dependency injection).

Guidance has created at first look almost empty interface:

C#
[ConcreteType("Firm.Product.Data.People.PersonDao, Firm.Product.Data")]
public interface IPersonDao : IDao<Person> { }

There are two points which must be mentioned. 

  1. The attribute. It’s value (supplied in constructor) is lately used in DaoFactory to produce object implementing IPersonDao interface 
  2. The list of declared methods – is the only available set of actions the upper layer has and can use. It’s clear for any C# developer, but it should be at least one spoken: Façades are working with the IPersonDao interface, what really means that, what is not declared that is not accessible.

Final not goes to parent interface IDao<T> which is armed with powerful set of basic methods. As a kick-off (providing basic CRUD) that will satisfy your needs:

C#
public interface IDao<T>
{
    T Add(T entity);
    T Update(T entity);
    void Delete(T entity);

    T GetById(IdT id);
    T GetById(IdT id, DateTime? historicalDateTime);
    IList<T> GetListByCriteria(ISearch searchObject);
}

EntityDao.cs

Base class methods have implemented almost everything to fulfill the CRUD circle. Only searching is Entity-dependent and you have to handle those customer needed requirements yourself.

If you were providing some (string type) properties in the Catharsis.Guidance, few criteria were created for you. They are a little bit unsafe allowing users to provide * for searching (in SQL syntax %). In production environment it could badly injure the performance of your application.

C#
public class PersonDao : DaoBase<Person>, IPersonDao
{
    public IList<Person> GetBySearch(ISearchObject<Person> searchObject)
    {
        CreateLike(Criteria, "Code", searchObject.Example.Code);
        CreateLike(Criteria, "SecondName", searchObject.Example.SecondName);
        CreateLike(Criteria, "FirstName", searchObject.Example.FirstName);
        return GetListByCriteria(searchObject);
    }
}

If users (attackers) will ask for ‘*words’ or ‘%words’, no indexes will be used to optimize the query. The database server then can be brought to troubles and unintended states (locks, etc.). Well, that is upon you to decide to allow that. Catharsis tries to be user-friendly, but you should know your users…

Entity.hbm.xml

The last needed file for Data layer is mapping XML. There are many copies of so-called ORM tools. But NHibernate natural separation of concern is exceptional. Why? In Catharsis, you'll meet NHibernate only on Data layer. And that’s almost shocking, because the Entity layer remains untouched, and upper levels don't know nothing about persistence.

Needed excurse to ORM 

ORM (object relational-database mapping) is in Catharsis stored in the Data project. Explaining .hbm.xml files is out of the scope this article. Anyway, you are expected to make your best to be NHibernate professional. You gain a lot, believe me.

Catharsis Guidance is doing a big part of needed mapping instead of you. Created xml file depends on a base class: Persistent entities are mapped as ‘class’, Tracked as ‘joined-subclass’. 

From the storage point of view, the Persistent entity (mapped as ‘class’) is stored in the separate table with defined primary key as auto incrementing. The C# base class Persistent is (from the storage point of view) virtual, without need to be stored. 

XML
<?xml version='1.0' encoding='utf-8' ?>
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'
    namespace='Firm.Product.Entity.People' assembly='Firm.Product.Entity'>
  <class name='Person' table='Person' lazy='true' >
      <id name='ID' column='PersonId' >
        <generator class='native'></generator>
      </id>
    <property not-null='true' name='Code' />
    <property not-null='true' name='SecondName' />
    <property not-null='true' name='FirstName' />
  </class>
</hibernate-mapping> 

The Tracked entities are opposite. The Tracked abstract class has even 5 tables in the storage, needed to provide the built-in tracking. Entity class must be mapped as ‘joined-subclass’ therefore (in C# inherited). Also do not forget to switch off auto incrementing for its table. The unique db key (the property ID) will be provided from Tracked base tables (and shared among all others).

XML
<?xml version='1.0' encoding='utf-8' ?>
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'
    namespace='Firm.Product.Entity.People' assembly='Firm.Product.Entity'>
  <joined-subclass name='Person' table='Person' lazy='true'
          extends='Firm.Product.Entity.Tracked, Firm.Product.Entity' >
    <key column='PersonId' />
    <property not-null='true' name='Code' />
    <property not-null='true' name='FirstName' />
    <property not-null='true' name='SecondName' />
  </joined-subclass>
</hibernate-mapping>

Back to Entity.hbm.xml

Catharsis allows you to forget on the above rows (almost). Guidance will prepare all needed stuff in the mapping .hbm.xml file dependently on the base class. You will only append property-column mapping for other properties. That’s all. 

Guidance is doing another very very very important thing for you. NHibernate can read .hbm.xml files only if they are embedded in DLL. That means that the property of any mapping file MUST be set to embedded resource. And that is done by Guidance for you as default!

Summary

It's always fine to know what's hidden inside. And even better is than forget and concern on business case. That's why Catharsis architecture and Guidance are there.

Catharsis Guidance creates on data level two files (project .data) and puts interface into .common project. Your next steps will only extend mapped properties in .hbm.xml file and adjust Dao for searching. If you'll decide to provide new special method on EntityDao object, do not forget to publish it in IEntityDao interface as well.

Source Code

Enjoy Catharsis.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)