|
Article relates to
|
Telerik OpenAccess ORM
|
|
Created by
|
Petar Petkov
|
|
Last modified
|
29.05.2009
|
|
Last modified by
|
Petar Petkov
|
Summary:
This article shows some basic ways to handle scenarios during different lifecycle events of our persistent objects.
Scenario:
For the purpose of this example we will create two sample classes – Address and Person:
Note the [Telerik.OpenAccess.Transient()] attribute above the years field. This attribute specifies that this field will exist in the class but its value wont be persisted in the database. Another attribute to mention is the FieldAlias attribute which gives us the permission to get advantage of the public properties in our queries, especially the LINQ.
Implementing IinstanceCallbacks Interface:
OpenAccess ORM can automatically perform application-specific operations on class objects during certain lifecycle events. By implementing the IInstanceCallbacks interface for a persistence capable class, user-defined methods are called when an object of the class is read from the database, when an object is removed (deleted) from the database, or when an object is written to the database. The IInstanceCallbacks interface methods are, respectively, PostLoad(), PreDelete(), and PreStore(). You must implement all three in your class. Let the Person class inherit the IinstanceCallBacks interface.
The PostLoad() method is called when the object is resolved, i.e., the object data is loaded from the database into memory. OpenAccess ORM uses delayed loading. Even if you have a reference to the object, the object data is not read from the database until some operation that requires the data is performed on the object. Only at this time is the object resolved. This means that the PostLoad() method is not necessarily called when you first get a reference to the object but rather when the object data is requested.
The PostLoad() method is particularly useful for initializing any transient fields whose values depend on persistent field values. In our example we have a Person class that has a transient field for the person's age, this can be calculated from the persistent birthdate field (and the current date) when a Person object is resolved.
The above code will generate the age of the person each time a person object is loaded.
- PreRemove (IObjectScope scope)
This method is called in the IObjectContext.Remove() method, i.e., when the object is removed (deleted) from the database (IObjectContext is the base interface for IObjectScope and ObjectContainer. The parameter scope can be used to propagate deletion to child objects. This propagation can also be configured in the mapping data. In our case we will use the PreRemove for the Person class. Each time a person is removed we will check if the address for this person is used by someone else and if not the address will be removed as well.
Note that the PreRemove is called before the transaction is commited so you don’t have to open a new transaction for deleting the address.
This method is called when a person object is written to the database during the transaction Commit() or Checkpoint() call. The PreStore() method is called before the object is actually written to the database. You could, for example, update persistent fields based on transient data, or, based on the results of any operations performed during the PreStore() call, actions such as throwing an exception and aborting the transaction may be taken. For the purpose of our example we will precalculate the persons age and if we are trying to store a person younger than 18 we will give the user 2 choices:
- Set a default birthdate for the new/modified person
- Abort the transaction.
Implementing IinitializeTransients Interface
Sometimes we might want to initziate our transients earlier than the PostLoad() event is fired. For example when we retrieve an object by its ID we are creating only the object reference but we are not acessing any field. In such case the transient fields wont be initzialized because no transient field has been accessed. In such cases we can use the IinitializeTransients interface.
We will need to implement its only method InitializeTransients(InitOperation initOperation). Note that we need extra care for this method as it can be fired for many occasions. For example when we create an object the method will be fired with initOperation equaling “Constructed”. The InitializeTransients will be fired for Refresh operations and even for transation.Begin() with the state of the object being Hallow. In order not to become resource consuming this method needs to be taken care of properly. In our example we will use the InitializeTransients to initialize our age property only on construction.
The InitializeTransients can be fired with the following initOperations :
| Apply |
when the information from a changeset is being applied. |
| Constructed |
when the object is being constructed. |
| CopyFrom |
when a object is being copied to a container |
| Hollow |
when the object is in hollow state(when transaction.begin()) is called |
| Refresh |
when the object is being refreshed using scope.Refresh(); |
Retrieve
|
when the object is being retrieved using the scope.Retrieve(); |
The complete source code can be found
here.