In this
post Rodrigo introduced the concept of
Transparent Persistence that makes developers life even easier as, once configured, db4o will take care of changed objects and make sure they are updated in the database (when the transaction gets committed) without developer assistance.
This level of transparency is, of course, a welcome addition but it introduces a few questions also; for instance in the following sample (assuming it was instrumented for Transparent Persistence),
1: using (IObjectContainer db = Db4oFactory.OpenFile(ConfigureTransparentPersistence(), DatabaseFileName))
2: {
3: Item item = db.Query<Item>( delegate(Item candidate) { return candidate.Value == 20; } )[0];
4: DisplayItem("Retrieved from database", item, obj => db.Ext().GetID(obj));
5:
6: item.Value = 1;
7: db.Rollback();
8: DisplayItem("After Rollback", item, obj => db.Ext().GetID(obj));
9: }
What should happen to
item? Had the transaction committed successfully this would be a easy one to answer: do nothing; but our poor transaction was rolled back and now we have two different views for
item.Value:
- Db4o view: item.Value: 20
- In memory view: item.Value: 10
Bellow we list some possible actions:
- Do nothing, i.e, keep in mind that this would yield two different views of the object (in-memory/database).
- Fetch the object from the database again, enforcing the in memory state to be consistent with the database state.
The second option decidedly would be the preferred way to go for most scenarios. Once our application is taking advantage of
Transparent Activation, implementing (2) is a matter of simple deactivating the object after rollback:
1: using (IObjectContainer db = Db4oFactory.OpenFile(ConfigureTransparentPersistence(), DatabaseFileName))
2: {
3: Item item = db.Query<Item>(delegate(Item candidate) { return candidate.Value == 20; })[0];
4: DisplayItem("Retrieved from database", item, obj => db.Ext().GetID(obj));
5: item.Value = 1;
6: db.Rollback();
7:
8: db.Deactivate(item, 1);
9:
10: DisplayItem("After Rollback", item, obj => db.Ext().GetID(obj));
11: }
Once deactivated, any access to
item will activate it, i.e, the object state will be fetched from database. So, the in memory state will be consistent with the database state again.
That's
not a big deal, right? Just one call (
Deactivate()) and we are done! We may be very proud of ourselves!
But wait... as the popular proverb says:
"The devil is on the details" let's dig a little bit more!
What's the meaning of that little
1 in the
Deactivate() call? As stated
here, it's the
deactivation depth, or, how deep in the object graph
db4o should go deactivating objects. So why we used 1? Because we have strict knowledge about the class structure (i.e, it doesn't aggregate other objects); this knowledge violates encapsulation! Also, what if we change the class and introduce an aggregate? We'll need to change the deactivation depth to 2 (or more, depending on the aggregated class structure). It's worse than that; consider the scenario in which lots of objects have changed before the transaction rolled back; we would need to keep track of each object in order to be able to deactivating them! It doesn't seem to be that easy anymore, does it?
Another option would be
db4o to automatically deactivate these objects for us; even though easily achieveable, this behavior could be counter-intuitive to some developers that may expected to see the value they set to the property.
Clearly, this is an application developer decision, so, we introduced
IRollbackStrategy interface which allows developers to be notified about rollbacks and take the action they see fit best in this scenario (updated object in memory was not persisted to the database due to a rollback).
The contract for this interface is pretty simple:
IRollbackStrategy.Rollback() will be called once per
changed object. Bellow we show a possible DeactivateRollbackStrategy implementation:
1: class RollbackDeactivateStrategy : IRollbackStrategy
2: {
3: public void Rollback(IObjectContainer container, Object obj)
4: {
5: container.Ext().Deactivate(obj);
6: }
7: }
Simple, right?
Bellow we have the same sample but without a call to Deactivate():
1: using (IObjectContainer db = Db4oFactory.OpenFile(ConfigureTransparentPersistence(), DatabaseFileName))
2: {
3: Item item = db.Query<Item>(delegate(Item candidate) { return candidate.Value == 20; })[0];
4: DisplayItem("Retrieved from database", item, obj => db.Ext().GetID(obj));
5: item.Value = 1;
6: db.Rollback();
7: DisplayItem("After Rollback", item, obj => db.Ext().GetID(obj));
8: }
And the steps required to configure Transparent Persistence support with a rollback strategy in place:
1: private static IConfiguration ConfigureTransparentPersistence()
2: {
3: IConfiguration config = Db4oFactory.NewConfiguration();
4: config.Add(new TransparentPersistenceSupport(new RollbackDeactivateStrategy()));
5: return config;
6: }
Now compile (but do not instrument) the sample application and run it to see the outcome.
As you can see the in memory state was not refreshed from database (Item class does not support Transparent Activation yet).
Now instrument the application and run it again (you can find Db4oTool.exe under bin/net-2.0 folder in db4o distribution):
Cool! Now, after a rollback, the in memory state was restored and is consistent to database state again.
Last, but not least, note that, for newly inserted objects, it's possible to observe different behaviors when rolling back; if the object (in memory state) haven't changed after being stored, no Rollback() call will be made for this object; by the other hand, if changes were applied after the object were stored we do get Rollback() called, and, when using DeactivateRollbackStrategy, the object gets deactivated.
What do you think?
See you.
Adriano