11. Transparent Persistence


The problem of updating deep object structures was briefly outlined in Structured objects chapter . Update Depth configuration setting gives a user a certain control over the object updating and allows to find a balance between performance and convenient object storage code. However, this balance is far from ideal:
- when update depth is zero, each piece of code should "know" how many levels of objects should be updated; this potentially creates lots of problems when the objects are refactored;
- when update depth is maximum performance can become very poor as many unchanged objects will be stored unnecessary.

The solution to this problem is to let db4o engine decide, which objects were modified and should be stored. This feature was introduced in db4o version 7.1 and was named Transparent Persistence. So how does it work?

1. Database should be configured to use TransparentPersistenceSupport.
2. Persistent classes available for Transparent Persistence must implement  IActivatable interface. This interface provides a #Bind()  method to bind an object to the object container's activator.
3. The object is bound to the IObjectContainer  when it is first stored or instantiated from the database.
4. When an object field is modified in the runtime,  #Activate()  method is called to register the object to be stored with the next commit. The difference from Transparent Activation is in the activation purpose: ActivationPurpose.Write is used for TP.
5. When the transaction is committed or the database is closed, db4o traverses the list of modified Activatable objects and persists them.
Well, that's enough of theory, let's look at an example.


    11.1. Transparent Persistence Example

       
    We will use Car and SensorReadout classes from Deep Graphs chapter. These are persistent classes, so if we want to use Transparent Persistence, we should make them "visible" to Transparent Persistence by implementing IActivatable  interface.

    using System;
    using Db4objects.Db4o;
    using Db4objects.Db4o.Activation;
    using Db4objects.Db4o.TA;
    namespace Db4odoc.Tutorial.F1.Chapter9
    {
    public class Car : IActivatable
    {
        private readonly String _model;
        private SensorReadout _history;
        
        [Transient]
        private IActivator _activator;
        public Car(String model)
        {
            this._model=model;
            this._history=null;
        }
        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()));
        }
        protected double PollOilTemperature()
        {
            return 0.1* CountHistoryElements();
        }
        protected double PollWaterTemperature()
        {
            return 0.2* CountHistoryElements();
        }
        public override String ToString()
        {
            Activate(ActivationPurpose.Read);
            return string.Format("{0}/{1}", _model, 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;
        }
    }
    }


    Note, that we've added an _activator  field, Bind  and Activate  methods to implement IActivatable  interface. In addition to that all methods that read or write object fields has got activate calls with a corresponding purpose.
    Similar modifications should be done to the SensorReadout class.
    Now we are ready to test how Transparent Persistence work. First we should configure the database to use TransparentPersistenceSupport before storing objects:        
    // storeCarAndSnapshots
    IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
    config.Common.Add(new TransparentPersistenceSupport());
    using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
    {
        Car car = new Car("Ferrari");
        for (int i = 0; i < 3; i++)
        {
            car.snapshot();
        }
        db.Store(car);
    }

    Ok, all the objects are stored.
    Now, let's retrieve all the stored objects and modify them:
    // modifySnapshotHistory
    IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
    config.Common.Add(new TransparentPersistenceSupport());
    using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
    {
        System.Console.WriteLine("Read all sensors and modify the description:");
        IObjectSet result = db.QueryByExample(typeof(Car));
        Car car = (Car)result.Next();
        SensorReadout readout = car.History;
        while (readout != null)
        {
            System.Console.WriteLine(readout);
            readout.Description = "Modified: " + readout.Description;
            readout = readout.Next;
        }
        db.Commit();
    }

    You can see that we do not have to call #store any more - all the objects are stored when #commit is called.
    Let's test that the modifications actually reached the database:
    // readSnapshotHistory
    IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
    config.Common.Add(new TransparentPersistenceSupport());
    using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
    {
        System.Console.WriteLine("Read all modified sensors:");
        IObjectSet result = db.QueryByExample(typeof(Car));
        Car car = (Car)result.Next();
        SensorReadout readout = car.History;
        while (readout != null)
        {
            System.Console.WriteLine(readout);
            readout = readout.Next;
        }
    }

    Yes, it is all as it should be. If you want to see the difference without Transparent Persistence, run the same examples withoutTransparentPersistenceSupport .


    11.2. Transparent Persistence Enhancement

    As we saw before enhancement tools can simplify the process for Transparent Activation. The same applies to Transparent Persistence. Actually Transparent Persistence enhancement implicitly provides TA for enhanced classes.
    For more information please refer to Enhancement chapter .


    11.3. Conclusion

    Transparent Persistence can considerably simplify the development process at the same time providing considerable performance benefits. For more information on Transparent Persistence please refer to our online reference  or your offline copy of the Reference documentation.   


    11.4. 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.Chapter9
    {
        public class TransparentPersistenceExample : Util
        {
            readonly static string YapFileName = Path.Combine(
                                   Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                                   "formula1.yap");  
            
            public static void Main(String[] args)
            {
                File.Delete(YapFileName);
                StoreCarAndSnapshots();
                ModifySnapshotHistory();
                ReadSnapshotHistory();
            }
            public static void StoreCarAndSnapshots()
            {
                IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
                config.Common.Add(new TransparentPersistenceSupport());
                using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
                {
                    Car car = new Car("Ferrari");
                    for (int i = 0; i < 3; i++)
                    {
                        car.snapshot();
                    }
                    db.Store(car);
                }
            }
            public static void ModifySnapshotHistory()
            {
                IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
                config.Common.Add(new TransparentPersistenceSupport());
                using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
                {
                    System.Console.WriteLine("Read all sensors and modify the description:");
                    IObjectSet result = db.QueryByExample(typeof(Car));
                    Car car = (Car)result.Next();
                    SensorReadout readout = car.History;
                    while (readout != null)
                    {
                        System.Console.WriteLine(readout);
                        readout.Description = "Modified: " + readout.Description;
                        readout = readout.Next;
                    }
                    db.Commit();
                }
            }
            public static void ReadSnapshotHistory()
            {
                IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
                config.Common.Add(new TransparentPersistenceSupport());
                using(IObjectContainer db = Db4oEmbedded.OpenFile(config, YapFileName))
                {
                    System.Console.WriteLine("Read all modified sensors:");
                    IObjectSet result = db.QueryByExample(typeof(Car));
                    Car car = (Car)result.Next();
                    SensorReadout readout = car.History;
                    while (readout != null)
                    {
                        System.Console.WriteLine(readout);
                        readout = readout.Next;
                    }
                }
            }
        }
    }

    using System;
    using Db4objects.Db4o;
    using Db4objects.Db4o.Activation;
    using Db4objects.Db4o.TA;
    namespace Db4odoc.Tutorial.F1.Chapter9
    {
    public class Car : IActivatable
    {
        private readonly String _model;
        private SensorReadout _history;
        
        [Transient]
        private IActivator _activator;
        public Car(String model)
        {
            this._model=model;
            this._history=null;
        }
        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()));
        }
        protected double PollOilTemperature()
        {
            return 0.1* CountHistoryElements();
        }
        protected double PollWaterTemperature()
        {
            return 0.2* CountHistoryElements();
        }
        public override String ToString()
        {
            Activate(ActivationPurpose.Read);
            return string.Format("{0}/{1}", _model, 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;
        }
    }
    }

    using System;
    using Db4objects.Db4o;
    using Db4objects.Db4o.Activation;
    using Db4objects.Db4o.TA;
    namespace Db4odoc.Tutorial.F1.Chapter9
    {
        public class SensorReadout : IActivatable
        {
            private readonly DateTime _time;
            private readonly Car _car;
            private String _description;
            private SensorReadout _next;
            [Transient]
            private IActivator _activator;
            protected SensorReadout(DateTime time, Car car, String description)
            {
                this._time = time;
                this._car = car;
                this._description = description;
                this._next = null;
            }
            public Car Car
            {
                get
                {
                    Activate(ActivationPurpose.Read);
                    return _car;
                }
            }
            public DateTime Time
            {
                get
                {
                    Activate(ActivationPurpose.Read);
                    return _time;
                }
            }
            public String Description
            {
                get
                {
                    Activate(ActivationPurpose.Read);
                    return _description;
                }
                set
                {
                    Activate(ActivationPurpose.Write);
                    _description = value;
                }
            }
            public SensorReadout Next
            {
                get
                {
                    Activate(ActivationPurpose.Read);
                    return _next;
                }
            }
            public void Append(SensorReadout readout)
            {
                Activate(ActivationPurpose.Write);
                if (_next == null)
                {
                    _next = readout;
                }
                else
                {
                    _next.Append(readout);
                }
            }
            public int CountElements()
            {
                Activate(ActivationPurpose.Read);
                return (_next == null ? 1 : _next.CountElements() + 1);
            }
            public override String ToString()
            {
                Activate(ActivationPurpose.Read);
                return String.Format("{0} : {1} : {2}", _car, _time, _description);
            }
            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;
            }
        }
    }

    using System;
    using Db4objects.Db4o.Activation;
    namespace Db4odoc.Tutorial.F1.Chapter9
    {
        public class TemperatureSensorReadout : SensorReadout
        {
            private readonly double _temperature;
            public TemperatureSensorReadout(DateTime time, Car car, string description, double temperature)
                : base(time, car, description)
            {
                this._temperature = temperature;
            }
            public double Temperature
            {
                get
                {
                    Activate(ActivationPurpose.Read);
                    return _temperature;
                }
            }
            public override String ToString()
            {
                Activate(ActivationPurpose.Read);
                return string.Format("{0} temp : {1}", base.ToString(), _temperature);
            }
        }
    }