|
4. Structured objects
It's time to extend our business domain with another class and see how db4o handles object interrelations. Let's give our pilot a vehicle.
namespace com.db4o.f1.chapter2
{
public class Car
{
string _model;
Pilot _pilot;
public Car(string model)
{
_model = model;
_pilot = null;
}
public Pilot Pilot
{
get
{
return _pilot;
}
set
{
_pilot = value;
}
}
public string Model
{
get
{
return _model;
}
}
override public string ToString()
{
return _model + "[" + _pilot + "]";
}
}
}
|
4.1. Storing structured objects
To store a car with its pilot, we just call set() on our top level object, the car. The pilot will be stored implicitly.
[storeFirstCar]
Car car1 = new Car("Ferrari");
Pilot pilot1 = new Pilot("Michael Schumacher", 100);
car1.Pilot = pilot1;
db.set(car1);
|
Of course, we need some competition here. This time we explicitly store the pilot before entering the car - this makes no difference.
[storeSecondCar]
Pilot pilot2 = new Pilot("Rubens Barrichello", 99);
db.set(pilot2);
Car car2 = new Car("BMW");
car2.Pilot = pilot2;
db.set(car2);
|
4.2. Retrieving structured objects
4.2.1. QBE
To retrieve all cars, we simply provide a 'blank' prototype.
[retrieveAllCarsQBE]
Car proto = new Car(null);
ObjectSet result = db.get(proto);
listResult(result);
|
OUTPUT: 2
BMW[Rubens Barrichello/99]
Ferrari[Michael Schumacher/100]
|
|
We can also query for all pilots, of course.
[retrieveAllPilotsQBE]
Pilot proto = new Pilot(null, 0);
ObjectSet result = db.get(proto);
listResult(result);
|
OUTPUT: 2
Rubens Barrichello/99
Michael Schumacher/100
|
Now let's initialize our prototype to specify all cars driven by Rubens Barrichello.
[retrieveCarByPilotQBE]
Pilot pilotproto = new Pilot("Rubens Barrichello",0);
Car carproto = new Car(null);
carproto.Pilot = pilotproto;
ObjectSet result = db.get(carproto);
listResult(result);
|
OUTPUT: 1
BMW[Rubens Barrichello/99]
|
What about retrieving a pilot by car? We simply don't need that - if we already know the car, we can simply ask it for its pilot directly.
4.2.2. Query API
To query for a car given its pilot's name we have to descend one level deeper in our query.
[retrieveCarByPilotNameQuery]
Query query = db.query();
query.constrain(typeof(Car));
query.descend("_pilot").descend("_name")
.constrain("Rubens Barrichello");
ObjectSet result = query.execute();
listResult(result);
|
OUTPUT: 1
BMW[Rubens Barrichello/99]
|
We can also constrain the pilot field with a prototype to achieve the same result.
[retrieveCarByPilotProtoQuery]
Query query = db.query();
query.constrain(typeof(Car));
Pilot proto = new Pilot("Rubens Barrichello", 0);
query.descend("_pilot").constrain(proto);
ObjectSet result = query.execute();
listResult(result);
|
OUTPUT: 1
BMW[Rubens Barrichello/99]
|
We have seen that descending into a query provides us with another query. You may also have noticed that the associations between query nodes look somehow bidirectional in our diagrams. Let's move upstream.
[retrievePilotByCarModelQuery]
Query carquery=db.query();
carquery.constrain(typeof(Car));
carquery.descend("_model").constrain("Ferrari");
Query pilotquery=carquery.descend("_pilot");
ObjectSet result=pilotquery.execute();
listResult(result);
|
OUTPUT: 1
Michael Schumacher/100
|
4.3. Updating structured objects
To update structured objects in db4o, we simply call set() on them again.
[updateCar]
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot = new Pilot("Somebody else", 0);
db.set(found);
result = db.get(new Car("Ferrari"));
listResult(result);
|
OUTPUT: 1
Ferrari[Somebody else/0]
|
Let's modify the pilot, too.
[updatePilotSingleSession]
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot.AddPoints(1);
db.set(found);
result = db.get(new Car("Ferrari"));
listResult(result);
|
OUTPUT: 1
Ferrari[Somebody else/1]
|
Nice and easy, isn't it? But wait, there's something evil lurking right behind the corner. Let's see what happens if we split this task in two separate db4o sessions: In the first we modify our pilot and update his car, in the second we query for the car again.
[updatePilotSeparateSessionsPart1]
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot.AddPoints(1);
db.set(found);
|
[updatePilotSeparateSessionsPart2]
ObjectSet result = db.get(new Car("Ferrari"));
listResult(result);
|
OUTPUT: 1
Ferrari[Somebody else/2]
|
Looks like we're in trouble. What's happening here and what can we do to fix it?
4.3.1. Update depth
Imagine a complex object with many members that have many members themselves. When updating this object, db4o would have to update all its children, grandchildren, etc. This poses a severe performance penalty and will not be necessary in most cases - sometimes, however, it will.
To be able to handle this dilemma as flexible as possible, db4o introduces the concept of update depth to control how deep an object's member tree will be traversed on update. The default update depth for all objects is 1, meaning that only primitive and String members will be updated, but changes in object members will not be reflected.
db4o provides means to control update depth with very fine granularity. For our current problem we'll advise db4o to update the full graph for Car objects by setting cascadeOnUpdate() for this class accordingly.
[updatePilotSeparateSessionsImprovedPart1]
Db4o.configure().objectClass(typeof(Car))
.cascadeOnUpdate(true);
|
[updatePilotSeparateSessionsImprovedPart2]
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot.AddPoints(1);
db.set(found);
|
[updatePilotSeparateSessionsImprovedPart3]
ObjectSet result = db.get(new Car("Ferrari"));
listResult(result);
|
OUTPUT: 1
Ferrari[Somebody else/3]
|
This looks much better.
Note that container configuration must be set before the container is opened.
We'll cover update depth as well as other issues with complex object graphs and the respective db4o configuration options in more detail in a later chapter.
4.4. Deleting structured objects
As we have already seen, we call delete() on objects to get rid of them.
[deleteFlat]
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
db.delete(found);
result = db.get(new Car(null));
listResult(result);
|
OUTPUT: 1
BMW[Rubens Barrichello/99]
|
Fine, the car is gone. What about the pilots?
[retrieveAllPilotsQBE]
Pilot proto = new Pilot(null, 0);
ObjectSet result = db.get(proto);
listResult(result);
|
OUTPUT: 1
Rubens Barrichello/99
|
Ok, this is no real surprise - we don't expect a pilot to vanish when his car is disposed of in real life, too. But what if we want an object's children to be thrown away on deletion, too?
4.4.1. Recursive deletion
You may already suspect that the problem of recursive deletion (and perhaps its solution, too) is quite similar to our little update problem, and you're right. Let's configure db4o to delete a car's pilot, too, when the car is deleted.
[deleteDeepPart1]
Db4o.configure().objectClass(typeof(Car))
.cascadeOnDelete(true);
|
[deleteDeepPart2]
ObjectSet result = db.get(new Car("BMW"));
Car found = (Car)result.next();
db.delete(found);
result = db.get(new Car(null));
listResult(result);
|
Again: Note that all configuration must take place before the ObjectContainer is opened.
Let's have a look at our pilots again.
[retrieveAllPilotsQBE]
Pilot proto = new Pilot(null, 0);
ObjectSet result = db.get(proto);
listResult(result);
|
4.4.2. Recursive deletion revisited
But wait - what happens if the children of a removed object are still referenced by other objects?
[deleteDeepRevisited]
ObjectSet result = db.get(new Pilot("Michael Schumacher", 0));
Pilot pilot = (Pilot)result.next();
Car car1 = new Car("Ferrari");
Car car2 = new Car("BMW");
car1.Pilot = pilot;
car2.Pilot = pilot;
db.set(car1);
db.set(car2);
db.delete(car2);
result = db.get(new Car(null));
listResult(result);
|
[retrieveAllPilotsQBE]
Pilot proto = new Pilot(null, 0);
ObjectSet result = db.get(proto);
listResult(result);
|
Houston, we have a problem - and there's no simple solution at hand. Currently db4o does not check whether objects to be deleted are referenced anywhere else, so please be very careful when activating this feature.
Let's clear our database for the next chapter.
[deleteAll]
ObjectSet cars=db.get(new Car(null));
while(cars.hasNext()) {
db.delete(cars.next());
}
ObjectSet pilots=db.get(new Pilot(null,0));
while(pilots.hasNext()) {
db.delete(pilots.next());
}
|
4.5. Conclusion
So much for object associations: We can hook into a root object and climb down its reference graph to specify queries. But what about multi-valued objects like arrays and collections? We will cover this in the
next chapter .
4.6. Full source
namespace com.db4o.f1.chapter2
{
using System;
using System.IO;
using com.db4o;
using com.db4o.f1;
using com.db4o.query;
public class StructuredExample : Util
{
public static void Main(String[] args)
{
File.Delete(Util.YapFileName);
ObjectContainer db = Db4o.openFile(Util.YapFileName);
try
{
storeFirstCar(db);
storeSecondCar(db);
retrieveAllCarsQBE(db);
retrieveAllPilotsQBE(db);
retrieveCarByPilotQBE(db);
retrieveCarByPilotNameQuery(db);
retrieveCarByPilotProtoQuery(db);
retrievePilotByCarModelQuery(db);
updateCar(db);
updatePilotSingleSession(db);
updatePilotSeparateSessionsPart1(db);
db.close();
db=Db4o.openFile(Util.YapFileName);
updatePilotSeparateSessionsPart2(db);
db.close();
updatePilotSeparateSessionsImprovedPart1(db);
db=Db4o.openFile(Util.YapFileName);
updatePilotSeparateSessionsImprovedPart2(db);
db.close();
db=Db4o.openFile(Util.YapFileName);
updatePilotSeparateSessionsImprovedPart3(db);
deleteFlat(db);
db.close();
deleteDeepPart1(db);
db=Db4o.openFile(Util.YapFileName);
deleteDeepPart2(db);
deleteDeepRevisited(db);
}
finally
{
db.close();
}
}
public static void storeFirstCar(ObjectContainer db)
{
Car car1 = new Car("Ferrari");
Pilot pilot1 = new Pilot("Michael Schumacher", 100);
car1.Pilot = pilot1;
db.set(car1);
}
public static void storeSecondCar(ObjectContainer db)
{
Pilot pilot2 = new Pilot("Rubens Barrichello", 99);
db.set(pilot2);
Car car2 = new Car("BMW");
car2.Pilot = pilot2;
db.set(car2);
}
public static void retrieveAllCarsQBE(ObjectContainer db)
{
Car proto = new Car(null);
ObjectSet result = db.get(proto);
listResult(result);
}
public static void retrieveAllPilotsQBE(ObjectContainer db)
{
Pilot proto = new Pilot(null, 0);
ObjectSet result = db.get(proto);
listResult(result);
}
public static void retrieveCarByPilotQBE(ObjectContainer db)
{
Pilot pilotproto = new Pilot("Rubens Barrichello",0);
Car carproto = new Car(null);
carproto.Pilot = pilotproto;
ObjectSet result = db.get(carproto);
listResult(result);
}
public static void retrieveCarByPilotNameQuery(ObjectContainer db)
{
Query query = db.query();
query.constrain(typeof(Car));
query.descend("_pilot").descend("_name")
.constrain("Rubens Barrichello");
ObjectSet result = query.execute();
listResult(result);
}
public static void retrieveCarByPilotProtoQuery(ObjectContainer db)
{
Query query = db.query();
query.constrain(typeof(Car));
Pilot proto = new Pilot("Rubens Barrichello", 0);
query.descend("_pilot").constrain(proto);
ObjectSet result = query.execute();
listResult(result);
}
public static void retrievePilotByCarModelQuery(ObjectContainer db)
{
Query carquery=db.query();
carquery.constrain(typeof(Car));
carquery.descend("_model").constrain("Ferrari");
Query pilotquery=carquery.descend("_pilot");
ObjectSet result=pilotquery.execute();
listResult(result);
}
public static void updateCar(ObjectContainer db)
{
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot = new Pilot("Somebody else", 0);
db.set(found);
result = db.get(new Car("Ferrari"));
listResult(result);
}
public static void updatePilotSingleSession(ObjectContainer db)
{
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot.AddPoints(1);
db.set(found);
result = db.get(new Car("Ferrari"));
listResult(result);
}
public static void updatePilotSeparateSessionsPart1(ObjectContainer db)
{
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot.AddPoints(1);
db.set(found);
}
public static void updatePilotSeparateSessionsPart2(ObjectContainer db)
{
ObjectSet result = db.get(new Car("Ferrari"));
listResult(result);
}
public static void updatePilotSeparateSessionsImprovedPart1(ObjectContainer db)
{
Db4o.configure().objectClass(typeof(Car))
.cascadeOnUpdate(true);
}
public static void updatePilotSeparateSessionsImprovedPart2(ObjectContainer db)
{
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
found.Pilot.AddPoints(1);
db.set(found);
}
public static void updatePilotSeparateSessionsImprovedPart3(ObjectContainer db)
{
ObjectSet result = db.get(new Car("Ferrari"));
listResult(result);
}
public static void deleteFlat(ObjectContainer db)
{
ObjectSet result = db.get(new Car("Ferrari"));
Car found = (Car)result.next();
db.delete(found);
result = db.get(new Car(null));
listResult(result);
}
public static void deleteDeepPart1(ObjectContainer db)
{
Db4o.configure().objectClass(typeof(Car))
.cascadeOnDelete(true);
}
public static void deleteDeepPart2(ObjectContainer db)
{
ObjectSet result = db.get(new Car("BMW"));
Car found = (Car)result.next();
db.delete(found);
result = db.get(new Car(null));
listResult(result);
}
public static void deleteDeepRevisited(ObjectContainer db)
{
ObjectSet result = db.get(new Pilot("Michael Schumacher", 0));
Pilot pilot = (Pilot)result.next();
Car car1 = new Car("Ferrari");
Car car2 = new Car("BMW");
car1.Pilot = pilot;
car2.Pilot = pilot;
db.set(car1);
db.set(car2);
db.delete(car2);
result = db.get(new Car(null));
listResult(result);
}
public static void deleteAll(ObjectContainer db) {
ObjectSet cars=db.get(new Car(null));
while(cars.hasNext()) {
db.delete(cars.next());
}
ObjectSet pilots=db.get(new Pilot(null,0));
while(pilots.hasNext()) {
db.delete(pilots.next());
}
}
}
}
|
--
generated by Doctor courtesy of db4objecs Inc.