Community & Support
Home / Community & Support / Knowledge Base / Telerik OpenAccess ORM / General / Implementing IInstanceCallbacks and IInitializeTransients

Implementing IInstanceCallbacks and IInitializeTransients

Article Info

Rating: Not rated

Article information

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:
[Telerik.OpenAccess.Persistent(IdentityField = "personID")] 
   public class Person 
    { 
        public Person() { } 
        private int personID; 
        [Telerik.OpenAccess.FieldAlias("personID")] 
        public int PersonID 
        { 
            get { return personID; } 
            set { personID = value; } 
        } 
        private string personName; 
        [Telerik.OpenAccess.FieldAlias("personName")] 
        public string PersonName 
        { 
            get { return personName; } 
            set { personName = value; } 
        } 
        private DateTime birthday; 
        [Telerik.OpenAccess.FieldAlias("birthday")] 
        public DateTime Birthday 
        { 
            get { return birthday; } 
            set { birthday = value; } 
        } 
        private string additionalInfo; 
        [Telerik.OpenAccess.FieldAlias("additionalInfo")] 
        public string AdditionalInfo 
        { 
            get { return additionalInfo; } 
            set { additionalInfo = value; } 
        } 
        private Address personAddress; 
        [Telerik.OpenAccess.FieldAlias("personAddress")] 
        public Address PersonAddress 
        { 
            get { return personAddress; } 
            set { personAddress = value; } 
        } 
        [Telerik.OpenAccess.Transient()] 
        private int years; 
 
        public int Years 
        { 
            get { return years; } 
            set { years = value; } 
        } 
public override string ToString() 
        { 
            return String.Format("person name: {0}\nperson address: {1}\nperson age: {2}\n", PersonName, PersonAddress.ToString(), Years); 
        } 
 

[Telerik.OpenAccess.Persistent(IdentityField = "addressId")]
public
 class Address 
    { 
        public Address() 
        { 
        } 
        private int addressId; 
        [Telerik.OpenAccess.FieldAlias("addressId")] 
        public int AddressId 
        { 
            get { return addressId; } 
            set { addressId = value; } 
        } 
        private string city; 
        [Telerik.OpenAccess.FieldAlias("city")] 
        public string City 
        { 
            get { return city; } 
            set { city = value; } 
        } 
        private string country; 
        [Telerik.OpenAccess.FieldAlias("country")] 
        public string Country 
        { 
            get { return country; } 
            set { country = value; } 
        } 
        private string postal; 
        [Telerik.OpenAccess.FieldAlias("postal")] 
        public string Postal 
        { 
            get { return postal; } 
            set { postal = value; } 
        } 
        public override string ToString() 
        { 
            return String.Format("\ncity: {0}\ncountry: {1}",City,Country); 
        } 
 
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.
public class Person:IInstanceCallbacks 
  • PostLoad()
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.
public void PostLoad() 
        { 
            //the post load method is fired when the first persistent property of the Person class is accessed 
            years = CalculateYears(Birthday, PersonName); 
        } 
 

private static int CalculateYears(DateTime Birthday,string personName) 
        { 
            int years = 0; 
            if (DateTime.Today.Month == Birthday.Month) 
            { 
                if (DateTime.Today.Day.CompareTo(Birthday.Day) < 0) 
                { 
                    years = DateTime.Today.Year - Birthday.Year - 1; 
                } 
                else if (DateTime.Today.Day.CompareTo(Birthday.Day) > 0) 
                { 
                    years = DateTime.Today.Year - Birthday.Year; 
                } 
                else 
                { 
                    Console.WriteLine("Today is {0}'s birthday", personName); 
                    years = DateTime.Today.Year - Birthday.Year; 
                } 
            } 
            else if (DateTime.Today.Month.CompareTo(Birthday.Month) < 0) 
            { 
                years = DateTime.Today.Year - Birthday.Year - 1; 
            } 
            else 
            { 
                years = DateTime.Today.Year - Birthday.Year; 
            } 
            return years; 
        } 
 
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.
public void PreRemove(IObjectScope objectScope) 
        { 
            //when the Person object is being removed this event is fired 
            //the following code checks if the address from which the person is points to another person 
            //and if not it removes the address as well. 
            int addressID = this.PersonAddress.AddressId; 
            var result = from c in objectScope.Extent<Person>() where c.PersonAddress.AddressId == addressID select c; 
            if (result.Count() == 1) 
            { 
                objectScope.Remove(objectScope.Extent<Address>().Where(c => c.AddressId == addressID)); 
            } 
        } 
 
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.
  • PreStore()
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:
  1. Set a default birthdate for the new/modified person
  2. Abort the transaction.
public void PreStore() 
        { 
            years = CalculateYears(Birthday, PersonName); 
            if (years < 18) 
            { 
                Console.WriteLine("Do you want to set the default birthdate(12/12/1988)?\ny/n"); 
                char k =char.Parse(Console.ReadLine().ToString()); 
                switch (k) 
                { 
                    case 'y' : birthday = DateTime.Parse("12/12/1987"); break
                    case 'n': throw new Exception("Transaction stopped");  
                } 
 
            } 
        } 
 

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.
Person prs =(Person) scope.GetObjectById(Database.OID.ParseObjectId(typeof(Person),"2")); 
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.
public void InitializeTransients(InitOperation initOperation) 
        { 
            if(InitOperation.Constructed.Equals(initOperation)) 
            { 
                years = CalculateYears(Birthday, PersonName); 
            } 
        } 
 
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.

Comments

There are no comments yet.
If you'd like to comment on this KB article, please, send us a Support Ticket.
Thank you!

Please Sign In to rate this article.