NPersist Observers

 

By: Mats Helander

Copyright © Mats Helander 2004

 

Table of Contents

 

NPersist Observers. 1

Table of Contents. 1

Introduction. 1

The Observer-Observable Pattern. 2

Document Overview.. 2

The IEventListener Interface. 3

When to Use the IEventListener Interface. 3

The IEventListener Interface Methods. 4

The Object Lifecycle Event Methods. 5

The Property Access Event Methods. 7

The IObserver interface. 8

Implementing an Observer 9

Using Your Observers. 11

The ObservingAttribute Attribute. 11

The IObservable interface. 13

Implementing the IObservable Interface. 13

Adding Observers to Your Observables. 14

The Context Manager Events. 14

Summary. 15

 

Introduction

 

The MatsSoft NPersist Framework allows you to intercept events in the lifecycles of your objects in a number of different ways, allowing you to customize the behavior of your persistent objects in a very rich way.

 

This document will describe the mechanisms available for lifecycle event interception in the NPersist framework.

 

For pure object validation aspects, you should also take a look at the NPersist IValidatable interface, described in the document NPersist Validation available in the documentation section at http://www.npersist.com

 

This document, however, will focus on general lifecycle event interception, and in particular on the NPersist implementation of the Observer-Observable pattern, which can be used for object validation, but also for other functionality such as logging and security.

 

The Observer-Observable Pattern

 

The Observer-Observable pattern is a well-known pattern used for letting Observer objects subscribe to certain events that take place in other, Observable objects.

 

The basic pattern is that you add an Observer to an Observable using something like the following code (in this case, myEmployee is the Observable):

 

myEmployee.AddObserver(myEmployeeObserver)

 

Then, in the Employee.Save() method, all the added EmployeeObservers are iterated through. On each one, the method OnSave () is called. We know this method is there because all observers have to implement the IObserver interface that we have created and which contains an OnSave() method.

 

The OnSave method takes one argument, savedObject, to which we pass the Me variable (or this in C#)

 

Public Sub Save()

 

    Dim observer As IObserver

 

    For Each observer in m_Observers

 

        observer.OnSave(Me)

 

    Next

 

    ‘... The standard object saving code

 

End Sub

 

You are then free to implement any kind of code you like in the OnSave method of your Observer objects.

 

For example, you could implement a LoggingObserver that logs all the save actions to a file, a ValidityObserver that makes sure the object is in a valid state before being saved and possibly even a SecurityObserver that enforces security policies and checks access control lists.

 

You can read more about the Observer-Observable pattern at:

 

http://www.exciton.cs.rice.edu/JavaResources/DesignPatterns/ObserverObservable.htm

 

Document Overview

 

With NPersist, your objects may implement the IEventListener interface, allowing you to enter code that should be executed each time an object of a class is created, fetched, updated or deleted.

 

Furthermore, you can create Observers, objects implementing the IObserver interface and that can add further behavior to the lifecycle events of your objects.

 

These observers can be attached to the Context Manager for domain-wide application or to persistent objects that implement the IObservable interface.

 

The basic idea is to implement methods called things like OnBeforePersistObject and OnAfterDeleteObject and that are called by the framework on the appropriate points in the lifetime of an object.

 

Unlike in the brief description of the Observer-Observable pattern in the previous section, you do not have to take care of calling the methods on your observers yourself – the NPersist framework will do this for you.

 

This document will show you how you can implement the IEventListener interface on your domain objects and how you can implement the Observer-Observable pattern with NPersist.

 

It will also show you a third way to intercept the common lifecycle events that are used for both the IEventListener and the Observers, which is to subscribe to the events of the ContextManager object.

The IEventListener Interface

 

Before we look at the IObserver interface we’ll begin by looking at the IEventListener interface.

 

This is an interface that your persistent domain objects can implement. It also contains methods like OnBeforePersistObject and OnAfterDeleteObject and such.

 

The big difference between implementing the IEventListener interface and using Observers is that with Observers, you can decide dynamically, at runtime, which Observers you want to apply to an object, a class or a domain.

 

With the IEventListener interface, on the other hand, the implementation you enter will always be called on all your objects – not depending on whether any Observers have been added during runtime or not.

 

An example should hopefully bring some clarity to this distinction.

 

When to Use the IEventListener Interface

 

Consider an Employee class. Now, you want to make sure that whenever an employee object is saved, regardless of what business process that is working with it, a note about this operation should be logged to a file.

 

In this case, you just let your Employee class implement the IEventListener interface and you add the code for logging a message to file in the OnAfterUpdateObject() method.

 

Whenever an employee has been saved to the database, the OnAfterUpdateObject() method with your logging code will be called by the framework.

 

But now, say that in most cases it is only administrators who can save updates to employees, but that there are also a few cases where non-administrators can update employees.

 

There are only a few such cases, and they are all in contained in a few, well defined business logic methods. Now you’d want to add a special logging behavior so that, for these methods, in addition to the save action being logged to file you want an email sent to the administrator.

 

In this case, you could use an Observer. Then, in the business methods where this logging behavior should be applied, you just add your email-sending observer to the employee objects, and the email will be sent if the employee is saved during the process.

 

Another reason to use Observers is if you can write generic code that can be applied on objects from several classes.

 

Say that you could rewrite your email-sending Observer so that it, when composing the email, it used reflection to write out the property names and values of the object being saved. Perhaps this wouldn’t make for as nicely formatted an email, but it might do.

 

In such a case, you could add your email-sending observer to any domain object that you wanted to apply the behavior to.

 

Getting back to the IEventListener interface, though, this is something you can use when you don’t want to have to add the behavior dynamically to your objects at runtime, but rather want your lifecycle event intercepting methods called every time, for every object of the class.

 

The IEventListener Interface Methods

 

Note that while of course you’ll need the stubs for all of the following methods in the domain classes that should implement IEventListener, you only have to add any actual code to the methods that you are interested in intercepting the events for.

 

The IEventListener interface contains the following methods:

 

 

Sub OnBeforeCreateObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterCreateObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

Sub OnBeforeInsertObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterInsertObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

Sub OnBeforeDeleteObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterDeleteObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

Sub OnBeforeRemoveObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterRemoveObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

Sub OnBeforePersistObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterPersistObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

Sub OnBeforeUpdateObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterUpdateObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

 

Sub OnBeforeGetObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterGetObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

Sub OnBeforeLoadObject(ByVal sender As Object, ByVal e As ObjectCancelEventArgs)

 

Sub OnAfterLoadObject(ByVal sender As Object, ByVal e As ObjectEventArgs)

 

 

Sub OnBeforePropertyGet(ByVal sender As Object, ByVal e As PropertyCancelEventArgs)

 

Sub OnAfterPropertyGet(ByVal sender As Object, ByVal e As PropertyEventArgs)

 

Sub OnBeforePropertySet(ByVal sender As Object, ByVal e As PropertyCancelEventArgs)

 

Sub OnAfterPropertySet(ByVal sender As Object, ByVal e As PropertyEventArgs)

 

 

Sub OnBeforeLoadProperty(ByVal sender As Object, ByVal e As PropertyCancelEventArgs)

 

Sub OnAfterLoadProperty(ByVal sender As Object, ByVal e As PropertyEventArgs)

 

 

 

First of all, we should note that the sender parameter will not contain a reference to the domain object that is being saved, deleted, etc. The sender parameter will instead contain a reference to the object that initiated the event.

 

This means that for the OnBeforeCreateObject and OnAfterCreateObject methods, for example, the sender parameter will contain a reference to the ContextManager object that the domain object was passed to for creation.

 

For the OnBeforeInsertObject and OnAfterInsertObject methods, however, the sender parameter will contain a reference to the SqlEngine object that generated the SQL Insert statement.

 

Usually, you don’t have to worry about the sender parameter. Just remember that it doesn’t contain a reference to the object that is being created, saved, etc. In order to get to that object, you should use the EventObject property in the e parameter.

 

However, when implementing the IEventListener interface in a domain class, you don’t have to use e.EventObject to get to the object in question – since you’re implementing an interface on the class, you’re already in the object you want to access, and you can access everything including its private members.

 

Just note that this will not be the case when we implement the IObserver interface, and then e.EventObject will come in handy, but more on that later.

The Object Lifecycle Event Methods

 

The second thing to note is that there are not only both Before and After versions of all the methods – there are events for Create and Insert events. This is because there’s a difference between when you ask the ContextManager to create an object and when the object is actually inserted into the database.

 

When you ask the context manager to create a new object by passing it to the CreateObject() method, it will only be registered as “up for creation”. Not until you call the PersistAll() method on the ContextManager will any SQL for inserting a new row for your objects actually be generated and sent to the database.

 

The OnBeforeCreateObject and OnAfterCreateObject methods will be called when you pass the new object to the CreateObject() method and the object is registered as up for creation.

 

The OnBeforeInsertObject and OnAfterInsertObject methods will be called when you call PersistAll() right before and after your object is actually inserted into the database.

 

Summing up, events will take place in the following order:

 

 

 

 

 

 

 

 

 

 

 

 

 

The same principle goes for the Delete and Remove methods, which are called when the object is passed to the DeleteObject() method and is registered as up for deletion, and when the object is actually removed from the database on the call to PersistAll(), respectively.

 

However, when we come to the Persist and Update methods the pattern changes a bit.

 

The Update methods correspond to the Insert and Update methods - they are called before and after the SQL Update statement or statements for saving the object to the database are generated and sent - so that’s not different.

 

The Persist methods have a lot in common with the Create and Delete methods, too. They are called inside the PersistObject() method of the context manager, just like the Create and Delete methods are called from the CreateObject() and DeleteObject() methods.

 

 The difference lies in that, while you’ll of course be calling the CreateObject() and DeleteObject() methods on your context manager in order to create and delete your objects, chances are you’re never going to call PersistObject(), and then the OnBeforePersistObject and OnAfterPersistObject  methods will not get invoked either!

 

The reason is that with NPersist, you normally never call PersistObject(), which persists just one object without waiting for a PersistAll.

 

Instead, you normally call the PersistAll method which will cause all the objects that are registered as up for creation to be inserted into the database, all the objects that are registered as up for deletion to be removed from the database, and all objects that have been registered as “modified but not saved” (they are registered as “Dirty”) will have their modified states saved to the database. 3D visualization. Great 3D rendering company. 3D model design.

 

While NPersist requires that you explicitly ask the ContextManager to register your objects as up for creation or up for deletion, it does not require that you register objects that you have modified but not saved as “Dirty” – this registration is handled automatically by the framework.

 

As soon as you write to the property of any of your objects, it will automatically be registered as “up for save” - or Dirty as it is normally called - thanks to the NPersist property access notification. You can read more about the NPersist property access notification in separate documentation available at http://www.npersist.com

 

All in all, this means that the OnBeforePersistObject and OnAfterPersistObject  methods are relatively useless, but they are there just in case you ever should use the PersistObject() method and want to be able to intercept it. Instead, in order to intercept object updates, use the OnBeforeUpdateObject and OnAfterUpdateObject methods.

 

Next, we have the GetObject and LoadObject methods. The GetObject methods are called each time before and after an object is requested by a client, even if the object was fetched from the cache.

 

The LoadObject methods are only called when an object is actually loaded with values from the database – i.e. the first time the object is loaded in a session or transaction and is not yet in the cache.

 

The Property Access Event Methods

 

Finally we have the PropertyGet, PropertySet and LoadProperty methods.

 

The PropertyGet and PropertySet methods are called before and after the properties of your object is read from or written to, respectively. In contrast to all the other methods in the IEventListener interface, you can’t rely on these methods always being called.

 

The reason is that in order for these methods to be called, you must implement complete property access notification on your properties. If you do this, then the PropertyGet and PropertySet methods will be called, of course, but they do rely on you implementing the full property access notification for being called, while the other IEventListener methods will always be called by the framework. Клиника проктолог. Сокол.

 

The LoadProperty methods are called before and after a lazy loading property is loaded with a value from the database. Lazy loading properties are properties that aren’t loaded with any value from the database until the property is accessed for the first time. This means that the LoadProperty methods won’t be called when properties are loaded with values from the database as part of a normal object loading operation.

 

The last thing we will note here before looking at an example is that the Before events have Cancel flags in their e parameters. By setting the Cancel property to True in your method implementations you will cancel the execution of the event – for example, you will not send the SQL for inserting the object into the database if you raise the Cancel flag in an OnBeforeInsertObject event.

 

The following example checks that the first and last names of a Person object are not empty before an object is registered as up for creation.

 

Public Sub OnBeforeCreateObject( _

    ByVal sender As Object, _

    ByVal e As Framework.ObjectCancelEventArgs) _

    Implements IEventListener.OnBeforeCreateObject

 

    If m_FirstName = "" Then Throw New Exception("First name must not be empty!")

    If m_LastName = "" Then Throw New Exception("Last name must not be empty!")

 

End Sub

 

The Create and Delete methods can be good places to put code that implements advanced object composition structures. For example, you could add code to the OnAfterCreateObject method that creates a couple of related objects and add references to them in the properties of the main object. Correspondingly, in the OnBeforeDeleteObject method you could make sure to delete all the related objects again.

 

The IObserver interface

 

The IObserver interface inherits the IEventListener interface and thus contains all the methods from that interface. In addition, it also contains the following four methods:

 

Sub OnBeforeSqlExecute(ByVal sender As Object, ByVal e As SqlExecutorCancelEventArgs)

 

Sub OnAfterSqlExecute(ByVal sender As Object, ByVal e As SqlExecutorEventArgs)

 

 

Sub OnBeforePersistAll(ByVal sender As Object, ByVal e As ContextCancelEventArgs)

 

Sub OnAfterPersistAll(ByVal sender As Object, ByVal e As ContextEventArgs)

 

 

The SqlExecute methods are called before and after any SQL statement is sent to the database by NPersist. This allows you to log, verify and even modify all SQL that is sent to the database by the NPersist framework.

 

The PersistAll methods are called before and after the operation in the PersistAll() method is executed.

 

Implementing an Observer

 

The big difference between the IObserver interface and the IEventListener interface is that your domain objects are supposed to implement the IEventListener interface while for IObserver you should create separate Observer classes that implement this interface.

 

One important implication of this is that when you develop the implementation for IEventListener you’ll have access to the private members of the domain class, since you’re implementing the interface on that class.

 

When you develop the implementation of IObserver in an Observer class, however, you will only have access to the public interface of the domain object, since you will have to access it via the EventObject property of the e parameter.

 

Let’s say that we wanted to take the code from our IEventListener example above, the one that checked the first and last names of a Person object, and move it into an Observer instead.

 

Since we now have to work with the public interface of our Person object, we can’t access the m_FirstName and m_LastName fields directly any more. We also have to remember to cast to the Person class, since e.EventObject is typed as System.Object.

 

The result is the following code in our PersonObserver class:

 

Public Sub OnBeforeCreateObject( _

    ByVal sender As Object, _

    ByVal e As Framework.ObjectCancelEventArgs) _

    Implements IEventListener.OnBeforeCreateObject

 

    If CType(e.EventObject, Person).FirstName = "" Then

        Throw New Exception("First name must not be empty!")

    End If

 

    If CType(e.EventObject, Person).LastName = "" Then

        Throw New Exception("Last name must not be empty!")

    End If

 

End Sub

 

Of course, when reading the Person properties using the public interface, we will invoke the property notification. Often, this is not a problem but is in fact the consistent and desired behavior, making sure that properties become lazy loaded if they have to be and so on, but sometimes it can cause a problem.

 

Specifically, in the PropertyGet and PropertySet methods, if you’re not careful, you can set off an infinite loop. In order to avoid this, recast the object to the IObjectHelper interface in such a situation.

 

Public Sub OnAfterPropertySet( _

    ByVal sender As Object, _

    ByVal e As Framework.PropertyCancelEventArgs) _

    Implements IEventListener.OnAfterPropertySet

 

    If e.PropertyName = “FirstName” Then

 

        If CStr(CType(e.EventObject, IObjectHelper).GetPropertyValue(“FirstName”)) = "" Then

            Throw New Exception("First name must not be empty!")

        End If

 

    End If

 

    If e.PropertyName = “LastName” Then

 

        If CStr(CType(e.EventObject, IObjectHelper).GetPropertyValue(“LastName”)) = "" Then

            Throw New Exception("Last name must not be empty!")

        End If

 

    End If

 

End Sub

 

Note that since the e parameter contains a Value property for the PropertyGet and PropertySet methods, and for the OnBeforePropertySet method it also contains a NewValue property, the example above could be rewritten as follows:

 

Public Sub OnBeforePropertySet( _

    ByVal sender As Object, _

    ByVal e As Framework.PropertyCancelEventArgs) _

    Implements IEventListener.OnBeforePropertySet

 

    If e.PropertyName = “FirstName” Then

 

        If CStr(e.NewValue) = "" Then

            Throw New Exception("First name must not be empty!")

        End If

 

    End If

 

    If e.PropertyName = “LastName” Then

 

        If CStr(e.NewValue) = "" Then

            Throw New Exception("Last name must not be empty!")

        End If

 

    End If

 

End Sub

 

 

As mentioned before, the idea behind an Observer is that you can implement an object with custom behavior that will be invoked at certain points in the lifecycles of your domain objects.

 

An Observer is thus any object you create that implements the IObserver interface, but you should not let your domain classes implement the IObserver interface – Observers are new objects that you add to the project and that usually do just one thing: implement the IObserver interface.

 

As with IEventListener, you’ll need to include stubs for all the methods in the IObserver interface in your observer, but you only have to add any code in those methods you are interested in intercepting the events for.

 

While you can use Observers for a wide range of things, the following is a short and powerful example that will let you log all SQL that is sent by NPersist to the database in your favorite logging framework:

 

Public Sub OnBeforeSqlExecute( _

    ByVal sender As Object, _

    ByVal e As Framework.SqlExecutorCancelEventArgs) _

    Implements IObserver.OnBeforeSqlExecute

 

    MyCoolLoggingFramework.Log(e.Sql)

 

End Sub

 

Using Your Observers

 

Once you have implemented an Observer class, for example a SqlLoggingObserver implementing the example above, you have to add it to your ContextManager in order to have your OnBeforeSqlExecute method invoked.

 

The following example will add a SqlLoggingObserver to the ContextManager:

 

Dim mySqlObserver As New SqlLoggingObserver

 

myContextManager.AddObserver(mySqlObserver)

 

Now, the SqlLoggingObserver implements behavior unspecific to any of the domain classes. But what happens if we create a PersonNameObserver that implemented our earlier example which verifies that a Person’s first and last names were not empty before an object is created?

 

If we just add a PersonNameObserver to the ContextManager, the OnBeforeCreateObject method in our observer will be called for all types of domain object that are being created, not just for Person objects. Of course, when we try to cast e.EventObject to a Person, our code will throw an exception.

 

So, how can we make sure that our PersonNameObserver is only called when Person objects are created, updated, deleted or fetched?

 

In fact, there are two ways you can do this. We’ll begin by looking at the Attribute-based approach.

The ObservingAttribute Attribute

 

By decorating your Observer classes with the ObservingAttribute Attribute, you can specify exactly which class – or which classes – an Observer should apply to.

 

In our example, you could make sure that the PersonNameObserver would only be called with events concerning Person objects by decorating the PersonNameObserver class as follows:

 

<Observing(GetType(Person))> Public Class PersonNameObserver

    Implements IObserver

 

If you want an Observer to apply to several classes, you can supply an array of types instead of just one type as in the example.

 

But what about an Observer such as our SqlLoggingObserver, that observes events that are not object specific?

 

It doesn’t contain any code in the object specific event methods, so it won’t crash when NPersist calls all those methods, but it’s rather unnecessary for NPersist to place all those calls and will only waste performance.

 

For this reason, you can pass a True value to the observeContext parameter of the ObservingAttribute Attribute constructor.

 

<Observing(True)> Public Class SqlLoggingObserver

    Implements IObserver

 

An Observer that is not decorated with any ObservingAttribute Attribute will always have all its methods called by the NPersist framework, but if the Observer is decorated with the attribute, the following applies:

 

 

 

Let’s say that we merge our two Observers, PersonNameObserver and SqlLoggingObserver, into just one class - SuperCoolObserver. We would then have to use the following decoration to our new Observer class:

 

<Observing(GetType(Person), True)> Public Class SuperCoolObserver

    Implements IObserver

 

This would make NPersist invoke our Observer with events concerning objects of the Person class and also with all context-level events.

 

Now, say that we create two new subclasses for the Person class, called Employee and Customer. Of course, we want our Observer to apply to objects from these classes as well. We then modify the decoration so that it takes an array with the types of our three classes:

 

<Observing(New Type() { GetType(Person), GetType(Employee), GetType(Customer) }, True)> _

Public Class SuperCoolObserver

    Implements IObserver

 

…or at least, so you’d think!

 

It turns out that using arrays as Attribute constructor parameters is not actually CLR-compliant, and that VB.NET does not support it. While the corresponding code in C# should work (I hear they are thinking of making it work in VB.NET as well) you will have to “stack” attributes in VB.NET, as follows:

 

<Observing(GetType(Person), True), _

Observing(GetType(Employee)), _

Observing(GetType(Customer))> _

Public Class SuperCoolObserver

    Implements IObserver

 

By using the ObservingAttribute Attribute you can specify exactly what classes your Observers should be observing. But sometimes you want to observe just a single or a few objects of a class.

 

In fact, that is the more standard application of the Observer-Observable pattern, as described in the introduction of this document. The option of adding context-level observers that apply to the classes you specify using ObservingAttribute Attributes is something of an “NPersist special”.

 

The IObservable interface

 

In order to be able to add Observers to specific domain objects, you have to let your domain objects implement the IObservable interface.

 

The IObservable interface has just two methods:

 

Sub AddEventListener(ByVal eventListener As IEventListener)

 

Function GetEventListeners() As ArrayList

 

As you can see, the AddEventListener method accepts an EventListener object, not an Observer object.

 

However, since IObserver inherits from IEventListener, passing Observer objects to the AddEventListener method works fine, although of course the four context-level methods (the SqlExecute and PersistAll methods) of an Observer you pass to this method will never be called.

 

If you want, you can also implement pure EventListener objects that implement just the IEventListener method and that you pass to this method.

 

Since only the methods of the IEventListener interface will be called when you add an Observer to an Observable domain object, implementing pure EventListeners for this occasion might feel cleaner. On the other hand, if you already have an Observer that could be used, by all means use it.

 

The IObservable interface should perhaps have been named IListenable instead, but since Observers are acceptable EventListeners the IObservable name makes sense and it follows the naming of the standard Observer-Observable pattern.

 

At any rate, if you let a domain class implement IObservable, you will be able to add Observers (or EventListeners) to just a single object at a time.

 

Implementing the IObservable Interface

 

The IObservable interface can be easily implemented by your domain classes as follows:

 

Implements IObservable

 

Private m_EventListeners As New ArrayList

 

Public Sub AddEventListener(ByVal eventListener As IEventListener) _

    Implements IObservable.AddEventListener

 

    If Not m_EventListeners.Contains(eventListener) Then

 

        m_EventListeners.Add(eventListener)

 

    End If

 

End Sub

 

Public Function GetEventListeners() As System.Collections.ArrayList _

    Implements IObservable.GetEventListeners

 

    Return m_EventListeners

 

End Function

 

In fact, NPersist contains a base class called Observable with an implementation just like the one listed above. If you want, you can make your domain classes observable by letting them inherit this base class, but since you can implement the IObservable interface so easily by just copy-pasting the example code above into your domain classes, you can just do that and save the .NET single-type inheritance for another base class.

 

Adding Observers to Your Observables

 

With the IObservable interface, you will be able to add Observers – or EventListeners – to specific domain objects dynamically at runtime.

 

The following example will add our PersonNameObserver to a specific Person object. It is assumed that the Person class implements the IObservable interface.

 

Dim pnObserver As New PersonNameObserver

 

myPerson.AddEventListener(pnObserver)

 

All you have to do is to implement the IObservable interface on your domain classes, and just as before the framework will be responsible for making sure the methods of any Observers that have been added to your objects are called at the appropriate points in the lifecycles of the objects.

 

The Context Manager Events

 

Finally, there is one more way to intercept the lifecycle events of the objects. The ContextManager contains events that correspond to all the methods in IObservable - that is, it has events such as BeforeCreateObject, AfterPropertyGet and BeforeSqlExecute.

 

By handling these events, you can do everything you can do in an Observer. For example, in order to log all the SQL that is sent by NPersist, you could do the following:

 

Private Sub m_ContextManager_BeforeSqlExecute( _

    ByVal sender As Object, _

    ByVal e As Framework.SqlExecutorCancelEventArgs) _

    Handles m_ContextManager.BeforeSqlExecute

 

    MyCoolLoggingFramework.Log(e.Sql)

 

End Sub

 

This example uses the VB.NET Handles keywords to set up the event handling for a context manager that is a member of the same class as the handler (and contained in the variable m_ContextManager). Of course you can also set up event handling dynamically in the standard way.

 

Summary

 

The NPersist framework contains a wealth of ways for you to intercept prominent events in the lifecycles of your objects.

 

You can let your domain objects implement the IEventListener interface in order to apply behavior that is always called on all the objects of the class.

 

You can add Observers dynamically to your context manager and to specific domain objects that implement the IObservable interface.

 

You can control which classes that should be observed by Observers added to the context manager by using the ObservingAttribute Attribute.

 

Finally you can handle the lifecycle events raised by the context manager.

 

All this allows for an extremely flexible and customizable behavior in your domain model.

 

You can learn a lot more about the general Observer-Observable pattern by searching for it on the Internet of course, and now you know how to apply what you learn to your NPersist domain objects.

 

Good Luck and Happy Hacking!

 

/Mats