10. Transparent Activation


Let's take a second look at the concept of Activation. We have seen how db4o uses a "depth" concept by default to activate objects to a specific depth when they are returned from a query.

Wouldn't it be a lot nicer, if an application would never have to worry about activating objects and if db4o could handle things transparently for us? This is what Transparent Activation was developed for.


    10.1. The Activation Problem


    We can reuse most of the code from the Deep Graphs chapter and get it to work with Transparent Activation.

    As a first step we should fill up our database with Car, Pilot and SensorReadout objects, so we have some objects to work with.

    // storeCarAndSnapshots
    Pilot pilot = new Pilot("Kimi Raikkonen", 110);
    Car car = new Car("Ferrari");
    car.Pilot = pilot;
    for (int i = 0; i < 5; i++)
    {
        car.snapshot();
    }
    db.Store(car);


    If we now rerun the code to traverse all cars and their sensor readings, we are again confronted with the same problem that we had before, we end up with some leaves of our object graph being null.  

    // retrieveSnapshotsSequentially
    IObjectSet result = db.QueryByExample(typeof (Car));
    Car car = (Car) result.Next();
    SensorReadout readout = car.History;
    while (readout != null)
    {
        Console.WriteLine(readout);
        readout = readout.Next;
    }



    10.2. Turning On Transparent Activation


    Let's configure db4o to run in Transparent Activation mode and let's try again:

    // retrieveSnapshotsSequentiallyTA
    IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
    config.Common.Add(new TransparentActivationSupport());
    using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
    {
        IObjectSet result = db.QueryByExample(typeof(Car));
        Car car = (Car)result.Next();
        SensorReadout readout = car.History;
        while (readout != null)
        {
            Console.WriteLine(readout);
            readout = readout.Next;
        }
    }


    Wow it worked! Is it really that easy? Principally yes. When db4o is run in Transparent Activation mode there are no surprises with null members that have not yet been read from the database.
                    

    10.3. Implementing IActivatable

                    

    When Transparent Activation is turned on, all objects that do not implement the Db4objects.Db4o.TA.IActivatable interface will be fully activated when they are used.

    Although we wont get any surprises with null objects when we work in this mode, access to the root of a deep graph may load the entire graph into memory and that can take a long time and use a lot of memory.

    To prevent immediate activation of a class you can implement the IActivatable  interface. Whenever db4o comes across an Activatable object while activating a graph of objects it will stop traversing the graph any deeper. db4o will "know" that it can activate Activatable objects on demand, so there is no reason to continue activation until these objects are really needed.

    For demonstration purposes we have made all classes used in this example Activatable and we have also added all the code required to activate by hand.

    Let's take a look at the Activatable version of our Car class:

    using System;
    using Db4objects.Db4o;
    using Db4objects.Db4o.Activation;
    using Db4objects.Db4o.TA;
    namespace Db4odoc.Tutorial.F1.Chapter8
    {
    public class Car : IActivatable
    {
        private readonly String _model;
        private Pilot _pilot;
        private SensorReadout _history;
        
        [Transient]
        private IActivator _activator;
        public Car(String model)
        {
            this._model=model;
            this._pilot=null;
            this._history=null;
        }
        public Pilot Pilot
        {
            get
            {
                Activate(ActivationPurpose.Read);
                return _pilot;
            }
            set
            {
                Activate(ActivationPurpose.Write);
                this._pilot = value;
            }
        }
        
        public Pilot PilotWithoutActivation
        {
            get { return _pilot; }
        }
        public String Model
        {
            get
            {
                Activate(ActivationPurpose.Read);
                return _model;
            }
        }
        
        public SensorReadout History
        {
            get
            {
                Activate(ActivationPurpose.Read);
                return _history;
            }
        }
        
        public void snapshot()
        {   
            AppendToHistory(new TemperatureSensorReadout(DateTime.Now,this,"oil", PollOilTemperature()));
            AppendToHistory(new TemperatureSensorReadout(DateTime.Now, this, "water", PollWaterTemperature()));
            AppendToHistory(new PressureSensorReadout(DateTime.Now, this, "oil", PollOilPressure()));
        }
        protected double PollOilTemperature()
        {
            return 0.1* CountHistoryElements();
        }
        protected double PollWaterTemperature()
        {
            return 0.2* CountHistoryElements();
        }
        protected double PollOilPressure()
        {
            return 0.3* CountHistoryElements();
        }
        public override String ToString()
        {
            Activate(ActivationPurpose.Read);
            return string.Format("{0}[{1}]/{2}", _model, _pilot, CountHistoryElements());
        }
        
        private int CountHistoryElements()
        {
            Activate(ActivationPurpose.Read);
            return (_history==null ? 0 : _history.CountElements());
        }
        
        private void AppendToHistory(SensorReadout readout)
        {
            Activate(ActivationPurpose.Write);
            if(_history==null)
            {
                _history=readout;
            }
            else
            {
                _history.Append(readout);
            }
        }
        public void Activate(ActivationPurpose purpose)
        {
            if(_activator != null)
            {
                _activator.Activate(purpose);
            }
        }
        public void Bind(IActivator activator)
        {
            if (_activator == activator)
            {
                return;
            }
            if (activator != null && null != _activator)
            {
                throw new System.InvalidOperationException();
            }
            _activator = activator;
        }
    }
    }


    Can you spot the member _activator, all the # Activate() calls and the two methods # Activate() and # Bind( IActivator) at the end?

    An Activatable class should store the Activator that db4o provides to the bind method in a transient variable and call # Activate() on this Activator before any field is accessed.

    If the object is already activated, the method will return immediately. If it is not, activation will happen at this time.

    We have added the PilotWithoutActivation property to the Car class to be able to demonstrate.

    // demonstrateTransparentActivation
    IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
    config.Common.Add(new TransparentActivationSupport());
    using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName)){
        IObjectSet result = db.QueryByExample(typeof (Car));
        Car car = (Car) result.Next();
        Console.WriteLine("#PilotWithoutActivation before the car is activated");
        Console.WriteLine(car.PilotWithoutActivation);
        Console.WriteLine("accessing 'Pilot' property activates the car object");
        Console.WriteLine(car.Pilot);
        Console.WriteLine("Accessing PilotWithoutActivation property after the car is activated");
        Console.WriteLine(car.PilotWithoutActivation);
    }



    10.4. Where Enhancement can help


    If all this Activatable code in a persistent class looked like a lot of typing work, do not worry: db4o comes with a tool to add this code automatically to all of your persistent classes. Read more about it in the chapter on Enhancement .  

    As a final step we should clean up the database again.

    // deleteAll
    IObjectSet result = db.QueryByExample(typeof(Object));
    foreach (object item in result)
    {
        db.Delete(item);
    }



    10.5. Conclusion


    This was just a short introduction to Transparent Activation and what it can do for you. For more detailed information please see the pages on Transparent Activation in our online reference  or in your offline copy of the Reference documentation.


    10.6. Full source


    using System;
    using System.IO;
    using Db4objects.Db4o;
    using Db4objects.Db4o.Config;
    using Db4objects.Db4o.TA;
    using Db4odoc.Tutorial.F1;
    namespace Db4odoc.Tutorial.F1.Chapter8
    {
        public class TransparentActivationExample : Util
        {
            readonly static string YapFileName = Path.Combine(
                                   Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                                   "formula1.yap");  
            
            public static void Main(String[] args)
            {
                File.Delete(YapFileName);
                using(IObjectContainer db = Db4oEmbedded.OpenFile(YapFileName))
                {
                    StoreCarAndSnapshots(db);
                }
                using(IObjectContainer db = Db4oEmbedded.OpenFile(YapFileName))
                {
                    RetrieveSnapshotsSequentially(db);
                }
                RetrieveSnapshotsSequentiallyTA();
                DemonstrateTransparentActivation();
            }

            public static void StoreCarAndSnapshots(IObjectContainer db)
            {
                Pilot pilot = new Pilot("Kimi Raikkonen", 110);
                Car car = new Car("Ferrari");
                car.Pilot = pilot;
                for (int i = 0; i < 5; i++)
                {
                    car.snapshot();
                }
                db.Store(car);
            }
            public static void RetrieveSnapshotsSequentially(IObjectContainer db)
            {
                IObjectSet result = db.QueryByExample(typeof (Car));
                Car car = (Car) result.Next();
                SensorReadout readout = car.History;
                while (readout != null)
                {
                    Console.WriteLine(readout);
                    readout = readout.Next;
                }
            }
            public static void RetrieveSnapshotsSequentiallyTA()
            {
                IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
                config.Common.Add(new TransparentActivationSupport());
                using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
                {
                    IObjectSet result = db.QueryByExample(typeof(Car));
                    Car car = (Car)result.Next();
                    SensorReadout readout = car.History;
                    while (readout != null)
                    {
                        Console.WriteLine(readout);
                        readout = readout.Next;
                    }
                }
            }
            public static void DemonstrateTransparentActivation()
            {
                IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
                config.Common.Add(new TransparentActivationSupport());
                
                using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName)){
                    IObjectSet result = db.QueryByExample(typeof (Car));
                    Car car = (Car) result.Next();
                    Console.WriteLine("#PilotWithoutActivation before the car is activated");
                    Console.WriteLine(car.PilotWithoutActivation);
                    Console.WriteLine("accessing 'Pilot' property activates the car object");
                    Console.WriteLine(car.Pilot);
                    Console.WriteLine("Accessing PilotWithoutActivation property after the car is activated");
                    Console.WriteLine(car.PilotWithoutActivation);
                }
            }
        }
    }