db4o: Transactions

So I showed the basics, explained the activation-system and pointed out how db4o manages object-identities. So far so good, but I’ve never explain a very important feature of most databases, the transactions. Time to catch up.

(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)

Transactions

Actually we never used transaction so far, right? So it’s like auto commit? Lets try it out. First we run a little application which stores a object. After that the application goes into an endless loop. What do you do with application which hangs like this? Right, kill that bastard. So we kill our application with the Task-Manager or the Visual-Studio-Debugger.

File.Delete("database.db4o");
using (var db = StartDataBase())
{
    db.Store(new SimpleObject("A New Object"));
    while (true)
    {
        Console.Out.WriteLine("Demo: Kill this process now!! (Yay, Ctrl Alt Delete");
        Console.Read();
    }
}

So our demo-application was terminated abnormally. So let’s check what is in our database.

using (var db = StartDataBase())
{
    var objects = (from SimpleObject o in db select o);
    AssertEquals(1, objects.Count());
}

Surprise, surprise, the test fails. Nothing is in the database. The reason is simple. Db4o automatically creates a transaction for you. As soon as you open the object-container a transaction is started. This transaction has the ACID-properties (Wikipedia, db4o) like other databases.

db4o transactions

db4o transactions

Controlling The Transaction

Well it’s wonderful that db4o creates the transaction by default. But it would be pretty useless if you cannot control it. Of course you can do that. The IObjectContainer interface has two methods for this: Commit()- and Rollback(). When you call those, the transaction is committed or rolled back and a new transaction is started immediately . For example:

File.Delete("database.db4o");
var db = StartDataBase();
try
{
    db.Store(new SimpleObject("Expect-To-Be-Committed"));
    db.Commit();
    db.Store(new SimpleObject("Expect-To-Be-Not-Committed"));
    throw new SomethingWentWrongException();
} catch(SomethingWentWrongException)
{
    db.Rollback();
} finally
{
    db.Dispose();
}
using (var checkDB = StartDataBase())
{
    var exists = (from SimpleObject o in checkDB
                  where o.Content.Equals("Expect-To-Be-Committed")
                  select o);
    AssertEquals(1, exists.Count());
    var wasNotCommited = (from SimpleObject o in checkDB
                          where o.Content.Equals("Expect-To-Be-Not-Committed")
                  select o);
    AssertEquals(0, wasNotCommited.Count());
}

So this works fine. By the way, the Close()- and Dispose()-methods call Commit() implicitly, so with the C# using-statement you commit automatically.

db4o-commit

db4o-rollback

Personally I use a wrapper around the transaction handling. It takes a closure which contains the db-operations and ensures that everything runs through or everything is rolled back:

dataBase.InTransaction(
    tx=>{
        db.Store(new DemoObject());
    });

Don’t Let Your Objects Fall Behind

Now to a really nasty detail of the db4o-transaction-handling. We retrieve a object and change a property. Then we update this object in the database with Store(). After that we rollback the transaction. The demo-code:

using(var db = StartDataBase())
{
    var single = (from SimpleObject o in db
                  select o).Single();
    single.Content = "New Value";
    db.Store(single);
    db.Rollback();
    AssertEquals("Old-Value", single.Content);
}

Unfortunately this test will fail =(. When you rollback a transaction your objects in memory aren’t rolled back automatically. So you have to be very careful when try to resume after a some kind of error. So if you want to set a object in the state of database you can use Refresh():

db.Rollback();
db.Ext().Refresh(single,int.MaxValue);
db4-state

Resume-Strategy

Now we know how we can commit and rollback a transaction. Also we know that when we rollback a transaction, the objects in memory keep their state. So when you want to resume after a rollback, you have to have some kind of strategy. This strategy heavily depends on your use case.

Isolation

In a client-server-model the isolation guarantees are important. Db4o has read-committed isolation properties

Next time

So far I’ve showed some basics of db4o. Of course this is only the tip of the iceberg. You can read up all the details in the documentation. So next time I give some tips on how to model your persisted classes.

Tagged on: ,

3 thoughts on “db4o: Transactions

  1. gamlerhart Post author

    What the InTransaction function looks like is up to you. A simple implementation can be a extension-method for the IObjectContainer which runs the closue and then commits on the container (Like here: https://gist.github.com/976768) . In a real world application the InTransaction function may does more. And probably you also use you own class instead of the object container directly.