Object-oriented database programming with db4o - Part 1

1. Storing Objects

Okay, let’s first create a new shape and store it into the database.

// Open a local database located at DB_PATH
// Think of IObjectContainer as an ADO.NET connection
using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   // Let's create a circle
   IShape circle = new Circle(new CPoint(1, 2), 5f);
   // ...and then store it using the container
   container.Set(circle);

   // Get all objects of type Circle in the database
   IObjectSet result = container.Get(typeof(Circle));

   // Check if we have one circle
   Assert.AreEqual(1, result.Count);
   // ...and it has equivalent attributes
   Assert.AreEqual(circle, result[0]);
   // ...actually, it is the same object
   Assert.AreSame(circle, result[0]);
}

What needs to be explained a bit is line 18, the retrieved object does not only have the same attributes as the original object, but it actually is the same object in memory (AreSame() uses the == checking while AreEqual() uses Equals() checking). The reason is because the object container caches the references of all objects stored or retrieved in a session and thus may return the exact references if these objects are requested via a query (in this case, the call to Get(typeof)).   (By default, weak references are used and thus these objects may be wiped out by the garbage collector if they are not referenced to in the application code - we can configure to use hard references instead, but this option may silently hold up memory.)  If the current object container is closed and the Get(typeof) is called on a different object container, the call to AreEqual() will pass but the call to AreSame() will fail.  This feature allows us to update objects without having to fetch them from the database as long as they are previously stored or retrieved in the same session, but can also be harmful at times if we forget about it, since we may happen to work with the local copy of an object while thinking that it, together with all its references, is from the database.

 

Deeply nested objects

Now, let’s create a more complex nested object

using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   Circle circle = new Circle(new CPoint(50, 20), Color.Pink, 20f);
   ShapeList list = new ShapeList();
   list.Add(circle);
   list.Add(new Line(null, null));
   container.Set(list);
}
using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   IObjectSet result = container.Get(typeof(ShapeList));
   // One shape list
   Assert.AreEqual(1, result.Count);
   ShapeList list = (ShapeList)result[0];
   // ...has 2 children
   Assert.AreEqual(2, list.Count);
   // ...has the right circle
   Circle circle = new Circle(new CPoint(50, 20), Color.Pink, 20f);
   Assert.AreEqual(circle, list[0]);
}

Notice that I use two separate sessions, by opening and closing the object container twice, that is to avoid the problem in which the object retrieved is actually the one already existing in memory (if we happen to perform all calls in one session).  The object stored, ShapeList, has several nested levels: first it has an internal List to store the list of objects of type IShape.  Within this internal list is a Circle and a Line objects which reference to some CPoint objects and Color structs.  Because the Equals() implementation of Circle does compare its center, the assertions above show that the object and its children (and its children’s children) are stored as expected.  See how easy the inheritance tree and nested objects are stored and retrieve with db4o? 

 

2. Updating Objects

Now, let’s see how we can update objects already existing in the database.  As already mentioned in the discussion about weak references, you need to either have “live” objects (still in the db4o’s references cache), by fetching them from the database or storing them in the same session, before being able to update them .

using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   // Create and store a new object
   Circle circle = new Circle(null, Color.Red, 0f);
   container.Set(circle);

   // Retrieve and update the color
   IObjectSet result = container.Get(typeof(Circle));
   Circle storedCircle = (Circle)result[0];
   storedCircle.Color = Color.Blue;
   container.Set(storedCircle);
}

// Start a new session to avoid using in-memory references
using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   IObjectSet result = container.Get(typeof(Circle));
   Assert.AreEqual(1, result.Count);
   Assert.AreEqual(Color.Blue, ((Circle)result[0]).Color);
}

Update Depth

Now, try the same update, but this time we will update the Point (center), instead of the Color and see what happens.

using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   CPoint point = new CPoint(10, 20);
   Circle circle = new Circle(point, 0f);
   container.Set(circle);

   IObjectSet result = container.Get(typeof(Circle));
   Circle storedCircle = (Circle)result[0];
   storedCircle.Center.X = 30;
   container.Set(storedCircle);
}
using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   IObjectSet result = container.Get(typeof(Circle));
   Assert.AreEqual(1, result.Count);
   Circle storedCircle = (Circle)result[0];

   // This will fail
   Assert.AreEqual(30, storedCircle.Center.X);
}

The last assertion will fail, storedCircle.Center.X returns 10, not 30.  How can this possibly happen?  The answer has something to do with the concept of Update Depth.  Imagine you have a highly nested object, e.g. a Person object with a list of friends, each of which has another list of friends, when updating the name of the root Person object, do you want to have all the hundreds of thousands of other friends objects persisted as well?  Obviously not, you would want to have the exact objects which are modified stored.  Since db4o currently does not have support for dirty-checking, it introduces the concept of Update Depth in order to allow application developers to control the level of references to be persisted with each update call.  The default Update Depth is 1, thus in line 10 of the above example, only modifications made on the Circle’s primitive attributes (of type int, bool, struct etc.) are persisted while those made to the CPoint are not.  That’s why we see the CPoint still has the old value in the assertion on line 19. 

However, note that if line 9 is replaced by the below statement, then the assertion will pass since CPoint is now a newly created object referenced to by the root (Circle) and db4o will store new children regardless of the Update Depth

storedCircle.Center = new CPoint(30, 20);

To make the code works as expected we can either increase the Update Depth or turn on cascading update for a specific type or for the whole database, as shown in the code segment below.

// 1: Turn on cascading update
container.Ext().Configure().CascadeOnUpdate(true);
// 2: Turn on cascading update for type Circle
container.Ext().Configure().ObjectClass(typeof(Circle)).CascadeOnUpdate(true);

// 3: Increase update depth
container.Ext().Configure().UpdateDepth(2);
// 4: Increase update depth for type Circle
container.Ext().Configure().ObjectClass(typeof(Circle)).UpdateDepth(2);

The above code only modifies the setting of a specific container, we can also makes the settings applied for all containers by calling the same methods in the IConfiguration instance returned by the call to method Db4objects.Db4o.Db4oFactory.Configure()

 

3. Deleting Objects

As like update, you need to either have “live” objects before being able to delete them.  To delete an object, we simple call the Delete() method of the object container with parameter as the object to be deleted.

using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   Circle circle = new Circle(new CPoint(1, 2), 0f);
   container.Set(circle);

   // Remove circle from db4o's referencing system
   container.Ext().Purge(circle);

   IObjectSet result = container.Get(typeof(Circle));
   Assert.AreEqual(1, result.Count);
   container.Delete(result[0]);
   Assert.AreEqual(0, container.Get(typeof(Circle)).Count);
}

In the above example, I use a different technique to make sure the object retrieved in line 9 is not the same in-memory object as the one stored in line 4, i.e. instead of closing the current session and opening another one for the retrieval, I use a call to the IObjectContainer#Ext()#Purge() method which will remove the object passed as parameter from the db4o’s internal reference management system. 

Now, the code above does show that the Circle object is deleted, but how about the CPoint object inside the Circle?  Is it also deleted?  Obviously, you and I would expect the CPoint object to be deleted when the Circle object is deleted since they have a composition relationship.  However, let’s think about the association and aggregation relationships, in which the referenced objects still make sense regardless of the existence of the referencing objects (e.g Album -> Song, Song -> Next Song etc.), will we still expect the referenced objects to be deleted?  The answer would be ‘no’ in most cases.  Since db4o does not know about the relationship semantics of our object model, it cannot choose to delete every referenced objects from the root object.  In fact, the following assertion if added after line 12 would pass, since the CPoint object is not deleted although its container (Circle) is.

Assert.AreEqual(1, container.Get(typeof(CPoint)).Count);

In order to have CPoint deleted, we need to turn on the cascade delete option for a specific type or for the whole database with the following calls

// 1: For the whole DB
container.Ext().Configure().CascadeOnDelete(true);
//2: For a specific type
container.Ext().Configure().ObjectClass(typeof(Circle)).CascadeOnDelete(true);

The last point of interest regarding object deletion is that if there is an object in the database referenced to by more than one references (in one or more objects) and that object is deleted, then all the references will be null when they are retrieved from the database.  This maybe an unexpected behavior when we delete a parent object with cascade delete enabled just to find out later that the children objects are also deleted while they are referenced to by some other parent objects.  The code segment below shows this unexpected behavior in action

using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   // Enable cascade delete for type Line
   container.Ext().Configure().ObjectClass(typeof(Line)).CascadeOnDelete(true);

   // A point shared by both lines
   CPoint sharedPoint = new CPoint(99, 100);
   Line line1 = new Line(sharedPoint, null);
   container.Set(line1);

   Line line2 = new Line(sharedPoint, null);
   container.Set(line2);

   // The shared point is also deleted with this call
   container.Delete(line1);
}
using (IObjectContainer container = Db4oFactory.OpenFile(DB_PATH))
{
   IObjectSet result = container.Get(typeof(Line));

   // line2 unexpectedly "lost" its point
   Assert.IsNull(((Line)result[0]).Start);
}

Pages: 1 2 3

9 Comments

Vu ToMarch 7th, 2007 at 11:26 pm

Good Tool!!! From now, I do not worry about data access layer anymore.

I’ve already intended to use Hibernate in my current java project ;), …but now I think I should change my mind after reading this article.

Thank Buu for your introduction obviously.
To^

Buu NguyenMarch 8th, 2007 at 12:52 am

I am glad that you like it, Vu. I got to know db4o when evaluating ORM frameworks to be used for a trivial personal .NET project. My first cut was to look at NHibernate and iBATIS.NET and while they are really good tools, I just did not think that it was worth the effort to use any of them in my project. That was when I found out db4o and decided to give it a try. Within about 6 hours I could learn about the API and coded the data layer of my application, something I could not even believe had I not tried it myself.

Of course there is still a long way for db4o to make itself widely adopted in the enterprise space like RDBMS products like Oracle or SQL Server, but I think there is a chance as db4o itself, the tooling, and the community surrounding it become more mature.

Buu Nguyen’s Blog » I am a dVPSeptember 19th, 2007 at 11:20 pm

[...] have just been recognized as a db4o Most Valued Professional (dVP) for the year 2008 and won a trip to Berlin next year to attend the ICOODB 2008 conference. It [...]

IdetrorceDecember 15th, 2007 at 8:25 pm

very interesting, but I don’t agree with you
Idetrorce

Chu_Thi_HueAugust 22nd, 2008 at 1:23 am

I want to create a button, when i click that button it will create a
new database (with db4o in Winds Form application (C#)). That code is:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using com.db4o;
using Db4oTools;
using System.IO;

//…………

private void btnThem_Click(object sender, EventArgs e)
{
//File.Delete(Util.YapFileName);
IObjectContainer db = Db4oFactory.OpenFile(Util.YapFileName);
try
{
//
}
finally
{
db.Close();
}

}

and i added: db4o and Db4oTools in References. But i couldn’t create a new database. So, can you help me? Thanks very much!

Buu NguyenAugust 22nd, 2008 at 9:41 am

@Hue:
I suggest you review to see if Util.YapFileName contains the correct link to the db file. If it does, can you describe in detail what the exact problem that you have is? Can you send the stack trace of the exception if any? If there’s no exception, how do you know if you couldn’t create a database?

Chu_Thi_HueAugust 23rd, 2008 at 8:06 pm

Thanks very much! I have just created a new database.
I am doing my “plan for graduating” about OODB and I must write a application ( C#.net and db4o). But i don’t have much document about OODB and db4o, i hope you will send for me if you have!
My name’s Hue. I’m a senior student in University of Technical Education HCM City. I am very glad when I know about you! :)

Buu NguyenAugust 25th, 2008 at 4:13 pm

@Hue:
A great deal of documentation about db4o is located at http://developer.db4o.com/Resources/view.aspx/Documentation (you will need to register for an account).

Or you can refer to this book http://www.amazon.com/Definitive-Guide-db4o-Stefan-Edlich/dp/1590596560, which is by far the only book on db4o. While the book does not contain much depth and is a bit out of date, it should be sufficient for newbies to get up to speed with db4o.

Nice to know you and good luck on your studying!

Chu_Thi_HueAugust 26th, 2008 at 10:17 pm

Thanks very much! And good luck to you everything!

Leave a comment

Your comment