db4o: Transparent-Persistence

It has been a while since I’ve wrote my last post about db4o. You may remember the post about the activation-mechanism. Don’t you think that this is quite painful? Activating objects with the right activation depth and so forth? Wouldn’t it be nice if db4o actually activates the objects as soon as you need then? Well db4o can do that, it’s called Transparent-Activation.

(All posts of this series: the basics, activation, object-identity, transactions, persistent classes, single container concurrency, Queries in Java, C# 2.0, client-server concurrency, transparent persistence, adhoc query tools)

activation pain

activation pain

Prepare The Persistent Classes

We don’t want to activate the object manually all over our codebase. Therefore a object has to activate it self. To achieve this, the class needs to implement the IActivatable-Interface. For example:

internal class Person : IActivatable
{
    private string _firstname;
    private Person _parent;

    [Transient] private IActivator _activator;

    public string Firstname
    {
        get
        {
            Activate(ActivationPurpose.Read);
            return _firstname;
        }
        set { _firstname = value; }
    }

   /** Other properties like above.
     Every time you read a field you need to call 
     Activate(ActivationPurpose.Read); first */

    public void Bind(IActivator activator)
    {
        if (_activator == activator)
            return;
        if (activator != null && _activator != null)
            throw new InvalidOperationException("cannot rebind with another activator");
        _activator = activator;
    }

    public void Activate(ActivationPurpose purpose)
    {
        if (null != _activator)
            _activator.Activate(purpose);
    }
}

Now db4o calls the Bind()-method for each object. As soon as a property is read the Activate-method is called. The activator now is responsible for reading the data from the database and set the fields.

It’s extremely important that the implementation ensures that the activator is called before any field is accessed. Notice also that the ‘_activator’-field is marked as transient. It quite useless to store the activator.

First Test Run

The first test-run is easy. We create a deep object-graph. Then we access it and check if we get the expected data. For example:

using (var db = OpenDB(FilePath))
{
    var me = (from Person p in db
              where p.Firstname.Equals("Roman")
              select p).Single();
    var adam = me.Father.Father.Father.Father.Father.Father.Father;
    AssertEqual("Adam", adam.Firstname);
}

When you just run the code it will throw a NullReferenceException. Because first you need to activate the support for Transparent Activation. To do this, add the Transparent Activation support in the configuration:

var cfg = Db4oEmbedded.NewConfiguration();
cfg.Common.Add(new TransparentActivationSupport());
var db = Db4oEmbedded.OpenFile(cfg, path); 

After adding the configuration-piece it works wonderful. No more activation-depth tweaking, no more NullReferenceException due to not activated objects. 

transparent activation at work

transparent activation at work

No Way! I’m Not Writing All This Boiler Plate Code

As you see above, writing Transparent Activation aware classes by hand is quite painful. All this boiler plate code distracts from the important stuff and introduces potential bugs. That’s why there’s a byte-code enhancer tool which adds this behavior for you.

There are two ways to achieve this: First there’s command-line tool which you can use manually or call from your build-script. Additionally there’s an MSBuild task. I use the MSBuild task here, since most people use Visual Studio anyway.

So it’s quite easy, you add an additional-task to your project-file. First you refer to the db4oTool.MSBuild-assembly. It’s up to you where to put it. In my projects its in the lib-directory of the project. The you specify what to want to enhance, done. Note the additional ‘debug’-flag. This also updates the debug-information to allow you to step through your code.

  <UsingTask AssemblyFile=".\lib\db4o\Db4oTool.MSBuild.dll" TaskName="Db4oTool.MSBuild.Db4oEnhancerMSBuildTask" />
  <ItemGroup>
    <Db4oEnhance Include="$(TargetPath)" />
  </ItemGroup>
  <Target Name="AfterBuild">
    <Db4oEnhancerMSBuildTask Assemblies="@(Db4oEnhance)" CommandLine="-debug"/>
  </Target>

Normally you create a separate assembly which contains only the persisted classes. Therefore you add this build-step only to the persisted-classes-project.

You can also mark the classes by an attribute and tell the enhancer-task to updated only the marked classes. For example:

[AttributeUsage(AttributeTargets.Class,AllowMultiple = false,Inherited = true)]
internal class PersistedAttribute : Attribute
{
}

And then:

<Db4oEnhancerMSBuildTask Assemblies="@(Db4oEnhance)" CommandLine="-by-attribute:Db4OSeries.PersistedAttribute"/>
 

bytecode enhancer

bytecode enhancer

Collections

You probably use collections in your persisted class like a list or a map. Since you cannot change the code of the official .NET-collections, you need to use the db4o Transparent Activation aware implementations of collections. For example:

[Persisted]
internal class Person
{
    private readonly IList<Person> knowsPeople = new ArrayList4<Person>();

    public IEnumerable<Person> KnowsPeople
    {
        get { return knowsPeople; }
    }

    public void GetToKnow(Person person)
    {
        knowsPeople.Add(person);
    }
}
db4o-collections 

Mix IActivatable With Non-Activatable

What happens when you mix and match IActivatable-classes with normal classes? Let’s do a little experiment:

internal class Pet
{
    public Pet(Pet otherPet)
    {
        OtherPet = otherPet;
        Name = otherPet.Name + "’s-child";
    } 

    public Pet(string name)
    {
        Name = name;
    } 

    public string Name { get; set; }
    public Pet OtherPet { get; set; }
}

Extend the Person class above with:

        public Pet Pet
        {
            get
            {
                Activate(ActivationPurpose.Read);
                return _pet;
            }
            set { _pet = value; }
        }
 
And access the a deep pet-object-graph:
 
         var me = (from Person p in db
               where p.Firstname.Equals("Roman")
               select p).Single();
         var adam = me.Father.Father.Father.Father.Father.Father.Father;
         var pet = me.Pet.OtherPet.OtherPet.OtherPet.OtherPet.OtherPet.OtherPet.OtherPet.OtherPet;
         AssertEqual("Adam", adam.Firstname);
         AssertEqual("Fun", pet.Name);
 

This work’s absolutely fine. The objects which are not activatable are just eagerly activated. For example: Some pure, simple Value-Object don’t need to be lazy loaded from the database. It’s just a small object and represents the end of the object-graph anyway.

mix transparent activation with normal objects

mix transparent activation with normal objects

Hybrid Approach

Now we’ve seen what Transparent Activation is. It’s really useful. But what if you want to stay in control and therefore avoid the bytecode-enhancer? Making every persisted class IActivateable by hand is just extremely painful.

Based on the the observation above you could use a hybrid approach: Most of the classes do not implement IActivatable. But you use the Transparent Activation aware collections of db4o. Then you have already a quite useful construct: Everything is eager activated excluding the collections. Additionally you could introduce a ‘TransparentActivated’-class. This class just represents a reference which is lazy loaded. So only ‘special’ references and the collections are transparently activated. The rest of the object graph is just loaded normally.

Take a looks at this small example (<<TA>> = Transparent Persistance used in this association):

 TA-Hybrid

So we could implement is like this:

internal class Person
{
    private Gender _gender;
    private IList<Address> _addresses = new ArrayList4<Address>();
    private readonly TransparentActivated<House> _house = new TransparentActivated<House>();

    public IEnumerable<Address> Addresses
    {
        get { return _addresses; }
    }

    public void Add(Address item)
    {
        _addresses.Add(item);
    }

    public House House
    {
        get { return _house.Value; }
        set { _house.Value = value; }
    }

    public Gender Gender
    {
        get { return _gender; }
        set { _gender = value; }
    }
}
/// <summary>
/// Use this class to interrupt the eager loading of the database data into memory:
/// this class represents a reference to <see cref="T"/>, whereas <see cref="T"/> is
/// lazy activated as soon as <see cref="Value"/> is used
/// </summary>
/// <typeparam name="T"></typeparam>
internal class TransparentActivated<T>
{
    private T _instance;
    [Transient]
    private IActivator _activator;

    public T Value
    {
        get
        {
            Activate(ActivationPurpose.Read);
            return _instance;
        }
        set { _instance = value; }
    }
    public void Bind(IActivator activator)
    {
        if (_activator == activator)
            return;
        if (activator != null && _activator != null)
            throw new InvalidOperationException("cannot rebind with another activator");
        _activator = activator;
    }

    public void Activate(ActivationPurpose purpose)
    {
        if (null != _activator)
            _activator.Activate(purpose);
    }
}
 

However, this  has a huge drawback: Now the ‘_house’-field has additional indirection. Therefore the object-graph is even deeper which isn’t free. Especially queries are more complex since more objects are involved.

Anyway this hybrid approach isn’t the indented use case for Transparent Activation. But it’s a little demonstration that you can use Transparent Activation also very selective.

Transparent Persistence

There we are, we have a nice solution to lazy load the object from the database as we need then. For this we make the persisted classes database aware. But wait! Why don’t the persisted objects also manage changes? A object could notice when a property changes and store the changes to the database. That’s exactly what Transparent Persistence does.

First we need to extend also the setter, like this:

public string Firstname
{
    get
    {
        Activate(ActivationPurpose.Read);
        return _firstname;
    }
    set
    {
        Activate(ActivationPurpose.Write);
        _firstname = value;
    }
}

 

As said, it’s way smarter to the do this with the bytecode-enhancer. Guess what, the byte-code-enhancer already does this. So you don’t have to change a thing. Just use the MSBuild-Task like above =).

Furthermore you have to change the configuration. Instead of the TransparentActivationSupport you add the TransparentPersistenceSupport:

cfg.Common.Add(new TransparentPersistenceSupport());

Now your ready for the easiest way to persist your objects. You have to store a object only once. From now on db4o knows that the object is persisted. The object stores all changes by itself. You get the objects from the database, manipulate the objects as you wish. As soon as you call commit, the changes are persisted. Isn’t this wonderful =) An small example:

using (var db = OpenDB(FilePath))
{
    var pers = new Person();
    pers.Add(new Address("Main.Street", "A City"));
    pers.House = new House { Name = "My House" };
    pers.Gender = Gender.Female;

    // initial store
    db.Store(pers);
}
using (var db = OpenDBWithTA(FilePath))
{
    var me = LoadPerson(db);
    // some changes, note, NO STORE
    me.Add(new Address("Holiday-House", "Other City"));
    me.Gender=Gender.Male;
    me.House.Name = "BiggerHouse";
}
using (var db = OpenDBWithTA(FilePath))
{
    var me = LoadPerson(db);
    // everything is stored =)
    AssertTrue(me.Addresses.Count() == 2);
    AssertTrue(me.Gender == Gender.Male);
    AssertTrue(me.House.Name == "BiggerHouse");
} 
transparent persistance

transparent persistance

Conclusion

Transparent Activation and Persistence are a powerful mechanisms which save you a lot of work. Use it wisely. =)

The example-project with transparent persistance: TransparentPersistance.zip

Tagged on: ,