With this code we’re ready to access a MS SqlServer database named test. You may have noticed that we add the configuration to a type, in our case it was ActiveRecordBase. I’ll explain why later in this article.

Starting the framework

So the configuration is ready, let’s start ActiveRecord using one of the overloads of the Initialize method:

ActiveRecordStarter.Initialize(source, typeof(Blog), typeof(Post));

The Initialize method should be called only once, as it makes no sense calling several times. It must be called before you start using the types, otherwise operations will fail with a message like “An ActiveRecord class was used but the framework seems not properly initialized. Did you forget about ActiveRecordStarter.Initialize()?”. Can it be clearer than that? :-) For web applications, consider putting the invocation of the Initialize method on Application_Start.

Now what happens when you invoke the Initialize? Well, lots of things. This is probably the most expensive ActiveRecord call and you ought to know why.

The initialization process

ActiveRecord's initialization process consists of several steps:

Initializing internal components

Setting up a NHibernate's Configuration instance per model hierarchy

Creating an ActiveRecordModel for each "ActiveRecord type" (any ordinary class that fulfils the requirements to be considered a mapping class, in other words a class that declares the ActiveRecordAttribute)

The model is populated with the information extracted from the class' attributes

Running a sequence of visitors

The first visitor connects models, which you might think as graph vertexes being connected, although there's no special information on the edges

The second visitor checks the semantics of the mapping declared by you. It basically searches for errors while inferring the eventual missing information. If one is found, it presents a big friendly exception message

The final visitor assumes that everything is OK and outputs the NHibernate xml mapping

At this point ActiveRecord is ready for the battle

Now let's analyze each step carefully:

Initializing internal components

There are two main players on ActiveRecord’s internals. The implementation of ISessionFactoryHolder, and the implementation of IThreadScopeInfo. The former is responsible to acquire a session from the right place when requested, and is type hierarchy aware (more about that in a second). The latter is responsible to bind the scope information to, well, somewhere that represents one activity. For ordinary application this would be the TLS data slots, for web application a different implementation of IThreadScopeInfo uses the HttpContext.Current.Items.

You can provide your own implementations if you want to. Just check the configuration reference in order to know how to specify new types.

We can say that 100% of ActiveRecord operations will interact with the ISessionFactoryHolder. That’s cause most operations requires a NHibernate’s ISession implementation and the ISessionFactoryHolder knows how to get one. The logic is pretty trivial: is there any active scope on this “logical activity” – it asks the IThreadScopeInfo to get – if not, it just opens a session and returns it. Otherwise it will ask the scope to provide the session.

By the way, scopes demand a separated article.

Setting up a NHibernate's Configuration instance per model hierarchy

Usually you will use ActiveRecord to access one – and only one – database. But what if you want to access more than one? Remember that we associated the configuration information with the ActiveRecordBase type? For ActiveRecord that means that any type that extends ActiveRecordBase will use that database configuration. To access a different database you’ll need to create an abstract class that extends ActiveRecordBase and configure it properly. Types that maps tables from this other database needs to extend your abstract type.

When ActiveRecord initializes it creates Configuration and associates with these base types, that I like to call root types. Later, when your Blog class asks for a session, ActiveRecord gets its root type, obtains the ISessionFactory associated with it and caches the association (type to ISessionFactory instance), and finally returns the session.

Creating an ActiveRecordModel for each "ActiveRecord type"

ActiveRecordModel is a graph node. It has information about a possible parent (used when you use type mapping hierarchy through discriminators or joined subclasses); all mapped fields, which are represented by a FieldModel; all mapped properties, which are represented by PropertyModel; and the same thing for the primary key and relations.

In an old implementation we used to work directly on the types, but it turned out that always querying the attributes was very expensive. With this new implementation we collect everything at once. The responsible for that is the ActiveRecordModelBuilder.

You may also note that all models are visitable:

public void Accept(IVisitor visitor)
{
visitor.VisitModel(this);
}

The visitor pattern is a life saver as it keeps you from checking for different types to act accordingly. But I have the feeling that most developers just don’t get it, which is a shame.

But I digress. ActiveRecordModelBuilder will be invoked for the types that you passed to the Initialize method on ActiveRecordStarter (or the set of types if you’ve specified one or more assemblies). At this point ActiveRecordStarter discards any type that does not have the ActiveRecordAttribute defined. So remember this if things are not working as expected :-)

There are exceptions though. Nested classes (those that NHibernate likes to call Components for some obscure reason) don’t need this attribute.

“What about abstract classes, should I put an ActiveRecordAttribute there too?” Depends on what you’re doing. In fact you should check this test case for clarification.

Running a sequence of visitors

At this point we have a collection of populated ActiveRecordModels. It’s time to act on them.

The first visitor connects models

The visitor is called GraphConnectorVisitor which explains all it does. However it only needs to collect the types that are using type mapping hierarchy through discriminators or joined subclasses, and nested classes (which NHibernate refers as Components). It also cares about Hilo and CollectionID mappings.

The second visitor checks the semantics of the mapping declared by you

Also with meaningful name: SemanticVerifierVisitor. In fact it does more than checking. It fills the information you omitted. This makes things easier for the next visitor. For example, our Blog has a “has many” relation with the Post.

This visitor goes to the Post type and tries to find the reverse relation, which in this case would be a relation “belongs to Blog”. If it finds it, it uses that property and the post table to fill the information required to have a “has many” relation working with NHibernate. In the end it would be equivalent to

The final visitor outputs the NHibernate xml mapping

That’s up to XmlGenerationVisitor. It also outputs the mapping xml to a file if debug is enabled. It has almost 1000 lines of code, but at the same time it’s pretty clean and easy to understand. There’s no intelligence there aside from generating correct indented Xml.

I hope this shed some more light on ActiveRecord. Stay tuned for the next article.

Nobody really talks about the real-world scenarios. It's easy to understand the mapping and the blog idea in a read-only scenario, but what happens when you change the Foreign Key inside the aggregated table and want to update and save the main record, for example?

Say, for instance, we have:

Patient => Case <= Doctor

Supposing we entered a wrong Patient ID in the case table and we want to fix, what happens when you change the foreign id in the Case table and try to save the record?

This is the scenario : Upon invoking the ActiveRecordBase<t>.Save(entity) I want to execute the ActiveRecordBase<t>.FindAll() method.The new inserted record could not get the details on the child classwhile the other record can. This was happened everytime I inserted anew record. I'm just wondering what is wrong.