Home > .NET, Java, OOAD, software engineering, Technologies > Object-oriented database programming with db4o – Part 1

Object-oriented database programming with db4o – Part 1

March 7th, 2007

In Part 1 and Part 2 of the article entitled The Legend of Data Persistence, I have introduced you to the O-R impedance mismatch, O-R/M tools, ODBMS and its advantages and disadvantages in comparison with RDBMS.  Now, once you have decided that you would use an ODBMS for your project, which ODBMS should you use?  This article, as a follow-up of The Legend of Data Persistence, aims to introduce you to one of today’s most popular ODBMS implementations, db4objects (db4o). 

In the first part of this article, I will introduce you to db4o and show you how to create, update, delete, and query objects with it.  In the second part, I will discuss about db4o’s more advanced topics such as transaction and concurrency, custom translator, encryption, networking database, and schema evolution etc.  You can file the source code in this article in the Resource section.

 

I. What is db4o

db4o is an open-source native ODBMS available for both .NET and Java platforms.  As a native ODBMS, there is no separate schema kept track by db4o, instead the database schema and application object model are exactly the same, hence, no mapping or transformation is required for persistence and querying.  db4o has support for inheritance, deeply nested objects as well as exposes comprehensive query APIs for application developers to queries stored objects. 

Regarding usage mode, db4o can be deployed either as an embedded database or as a server-side database.  Finally, db4o has support for the schema evolution, indexing, transaction and concurrency, database encryption, and replication service (among db4o databases and certain relational databases).  The latest version of db4o is 6.1 and available under two licenses: GPL and a commercial runtime license.

Okay, enough for an introduction, let’s get to the code

 

II. The Domain Model

While db4o’s strengths are more obvious in applications with highly complex object model, the purpose of this article is more to offer an introduction to db4o, instead of exploring it in every level of dept.  As a result, I will use an object model which is very simple but still comprehensive enough to demonstrate features of db4o. 

What we have is an object model for a painting application.  There are two concrete shape types, Line, Circle, and ShapeList, which implement the IShape interface which has Paint() as its single method.  The ShapeList implements the composite design pattern and contains many instances of type IShape.  The CPoint represents a 2-D coordination used by Line (start point and end point) and Circle (center point).  (Yes,  I know there is a System.Drawing.Point, but that Point is a struct, not a class, and since Line and Circle already consist of System.Drawing.Color which is a struct, it is better to have a custom Point class instead to see the differences in how db4o handles classes and structs.)

db4o_model.png

 

III. Now, the Code

I will write code using C# 2.0.  If you are from the Java space, you should still easily understand the code because the db4o libraries for the two platforms are virtually identical. 

The first step we need to do is to download the binary of db4o from its website, and then in VS.NET 2005, add a reference to the Db4objects.Db4o.dll file which is located in the net-2.0 folder of the download. 

Below is what the solution explorer looks like after we’ve added the references as well as created the source files needed. 

db4o_solution.png

The source code for the model classes are as follows

 

IShape.cs

public interface IShape
{
   void Paint(Graphics graphics);
}

CPoint.cs

public class CPoint
{
   private int x;
   private int y;

   public CPoint(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

   public int X
   {
      get {return x;}
      set {x = value;}
   }

   public int Y
   {
      get {return y;}
      set {y = value;}
   }

   public override bool Equals(object obj)
   {
      if (obj == null || !(obj is CPoint))
      {
         return false;
      }
      CPoint point = obj as CPoint;
      return point.x == x && point.y == y;
   }

   public override int GetHashCode()
   {
      return x.GetHashCode() + y.GetHashCode();
   }

   public override string ToString()
   {
      return string.Format("Point [{0}, {1}]", x, y);
   }
}

Circle.cs

public class Circle : IShape
{
   private CPoint center;
   private Color color;
   private float radius;

   public Circle(CPoint center, float radius) : this(center, new Color(), radius)
   {
   }

   public Circle(CPoint center, Color color, float radius)
   {
      this.center = center;
      this.color = color;
      this.radius = radius;
   }

   public CPoint Center
   {
      get {return center;}
      set {center = value;}
   }

   public Color Color
   {
      get {return color;}
      set {color = value;}
   }

   public float Radius
   {
      get {return radius;}
      set {radius = value;}
   }

   public override bool Equals(object obj)
   {
      if (obj == null || !(obj is Circle))
      {
         return false;
      }
      Circle circle = obj as Circle;
      return (circle.center == null ? center == null : circle.center.Equals(center))
          && (circle.radius == radius)
          && (circle.color.Equals(color));
   }

   public override int GetHashCode()
   {
      return center == null ? 0 : center.GetHashCode()
          + color.GetHashCode() + radius.GetHashCode();
   }

   public override string ToString()
   {
      return string.Format("Circle [{0}, {1}, {2}]", center, radius, color);
   }

   public void Paint(Graphics graphics)
   {
      // No implementation
   }
}

Line.cs

public class Line : IShape
   private CPoint start;
   private CPoint end;
   private Color color;

   public Line(CPoint start, CPoint end) : this (start, end, new Color())
   {
   }

   public Line(CPoint start, CPoint end, Color color)
   {
      this.start = start;
      this.end = end;
      this.color = color;
   }

   public CPoint Start
   {
      get {return start;}
      set {start = value;}
   }

   public CPoint End
   {
      get {return end;}
      set {end = value;}
   }

   public Color Color
   {
      get {return color;}
      set {color = value;}
   }

   public override bool Equals(object obj)
   {
      if (obj == null || !(obj is Line))
      {
         return false;
      }
      Line line = obj as Line;
      return (line.start == null ? start == null : line.start.Equals(start))
          && (line.end == null ? end == null : line.end.Equals(end))
          && (line.color.Equals(color));
   }

   public override int GetHashCode()
   {
      return (start == null ? 0 : start.GetHashCode())
           + (end == null ? 0 : end.GetHashCode())
           + color.GetHashCode();
   }

   public override string ToString()
   {
      return string.Format("Line [{0}, {1}, {2}]", start, end, color.Name);
   }

   public void Paint(Graphics graphics)
   {
      // No implementation
   }
}
}

ShapeList.cs

public class ShapeList : IShape
   private IList shapeList;

   public ShapeList()
   {
      shapeList = new List();
   }

   public void Add(IShape shape)
   {
      shapeList.Add(shape);
   }

   public void Remove(IShape shape)
   {
      shapeList.Remove(shape);
   }

   public int Count
   {
      get {return shapeList.Count;}
   }

   public IShape this[int index]
   {
      get {return shapeList[index];}
      set {shapeList[index] = value;}
   }

   public override bool Equals(object obj)
   {
      if (obj == null || !(obj is ShapeList))
      {
         return false;
      }
      ShapeList otherList = obj as ShapeList;
      return shapeList.Equals(otherList);
   }

   public override int GetHashCode()
   {
      return shapeList.GetHashCode();

   }

   public override string ToString()
   {
      return shapeList.ToString();
   }

   public void Paint(Graphics graphics)
   {
      // No implementation
   }
}

Notes about the coding styles:

  • I will use unit test cases to explore and validate the behaviors of db4o, instead of dumping the output to the console.  I would love to hear whether this approach makes it easier to understand the code or not. 
  • I will try to explain the code by using as many comments embedded in the code as possible.  That will eliminate much of the text I would have to write (and you would have to read) had I not written the comments but will make the comments more elaborated than I, and possibly some of you, would expect to see in production code.

 

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);
}
  1. Vu To
    March 7th, 2007 at 23:26 | #1

    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^

  2. March 8th, 2007 at 00:52 | #2

    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.

  3. Idetrorce
    December 15th, 2007 at 20:25 | #3

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

  4. Chu_Thi_Hue
    August 22nd, 2008 at 01:23 | #4

    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!

  5. August 22nd, 2008 at 09:41 | #5

    @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?

  6. Chu_Thi_Hue
    August 23rd, 2008 at 20:06 | #6

    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! :)

  7. August 25th, 2008 at 16:13 | #7

    @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!

  8. Chu_Thi_Hue
    August 26th, 2008 at 22:17 | #8

    Thanks very much! And good luck to you everything!

  9. Hasan
    March 24th, 2010 at 12:07 | #9

    Can anyone help me to create a new database in db4o?
    i couldnt understand the explaining above.
    thanx in advance.

  10. March 24th, 2010 at 13:02 | #10

    @Hasan: Db4oFactory.OpenFile(DB_PATH) should create a new database if one doesn’t exist, have you checked?

  11. ali
    July 15th, 2010 at 20:09 | #11

    hi everybody, can anyone help me to create a client\server program with db4o?
    i will that program work on a lan network, i have a PC as a server on network and more other PCs as clients
    should i write server and client program separately ?

  12. Mehul
    February 2nd, 2012 at 17:56 | #12

    Hi,

    The post is really good. I still have a doubt. I am new to db4o so please bear with me. As what i know db4o also stores data into file which is exactly same as java serialization. Then what’s the difference between them and why it’s called OODB? In that case serialization or any other persistent storage mechanism must be also be called OODB. what makes db4o to be called as database?

    Thanks,
    Mehul

Comments are closed.