| # 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 Private _model As String Private _pilot As Pilot Public Sub New(ByVal model As String) _model = model _pilot = Nothing End Sub Public Property Pilot() As Pilot Get Return _pilot End Get Set _pilot = value End Set End Property Public ReadOnly Property Model() As String Get Return _model End Get End Property Public Overloads Overrides Function ToString() As String Return String.Format("{0}[{1}]", _model, _pilot) End Function End Class End Namespace`
## 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] Dim car1 As Car = New Car("Ferrari") Dim pilot1 As Pilot = 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] Dim pilot2 As Pilot = New Pilot("Rubens Barrichello", 99) db.[Set](pilot2) Dim car2 As Car = 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] Dim proto As Car = New Car(Nothing) Dim result As ObjectSet = 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] Dim proto As Pilot = New Pilot(Nothing, 0) Dim result As ObjectSet = db.[Get](proto) ListResult(result)`

OUTPUT: 2 Michael Schumacher/100 Rubens Barrichello/99

Now let's initialize our prototype to specify all cars driven by Rubens Barrichello.

`[retrieveCarByPilotQBE] Dim pilotproto As Pilot = New Pilot("Rubens Barrichello", 0) Dim carproto As Car = New Car(Nothing) carproto.Pilot = pilotproto Dim result As ObjectSet = 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 access the pilot field directly.

## 4.2.2. Native Queries

Using native queries with constraints on deep structured objects is straightforward, you can do it just like you would in plain other code.
Let's constrain our query to only those cars driven by a Pilot with a specific name:

`Public Class RetrieveCarsByPilotNamePredicate Inherits Predicate ReadOnly _pilotName As String Public Sub New(ByVal pilotName As String) _pilotName = pilotName End Sub`

`[retrieveCarsByPilotNameNative] Dim pilotName As String = "Rubens Barrichello" Dim results As ObjectSet = db.Query(New RetrieveCarsByPilotNamePredicate(pilotName)) ListResult(results)`

OUTPUT: 1 BMW[Rubens Barrichello/99]

Using .NET 2.0 syntax this is a lot simpler:
C# .NET 2.0

`[retrieveCarsByPilotNameNative] string pilotName = "Rubens Barrichello"; List<Car> results = db.Query<Car>(delegate(Car car) { return car.Pilot.Name == pilotName; }); listResults(results);`

## 4.2.3. SODA Query API

In order to use SODA for querying for a car given its pilot's name we have to descend two levels into our query.

`[retrieveCarByPilotNameQuery] Dim query As Query = db.Query() query.Constrain(GetType(Car)) query.Descend("_pilot").Descend("_name").Constrain("Rubens Barrichello") Dim result As ObjectSet = 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] Dim query As Query = db.Query() query.Constrain(GetType(Car)) Dim proto As Pilot = New Pilot("Rubens Barrichello", 0) query.Descend("_pilot").Constrain(proto) Dim result As ObjectSet = query.Execute() ListResult(result)`

OUTPUT: 1 BMW[Rubens Barrichello/99]

We have seen that descending into a query provides us with another query. Starting out from a query root we can descend in multiple directions. In practice this is the same as ascending from one child to a parent and descending to another child. We can conclude that queries turn one-directional references in our objects into true relations. Here is an example that queries for "a Pilot that is being referenced by a Car, where the Car model is 'Ferrari'":

`[retrievePilotByCarModelQuery] Dim carQuery As Query = db.Query() carQuery.Constrain(GetType(Car)) carQuery.Descend("_model").Constrain("Ferrari") Dim pilotQuery As Query = carQuery.Descend("_pilot") Dim result As ObjectSet = 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] Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) 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] Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) 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:

`[updatePilotSeparateSessionsPart1] Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) found.Pilot.AddPoints(1) db.[Set](found)`

And in the second, we'll double-check our modification:

`[updatePilotSeparateSessionsPart2] Dim result As ObjectSet = db.[Get](New Car("Ferrari")) ListResult(result)`

OUTPUT: 1 Ferrari[Somebody else/2]

Looks like we're in trouble: Why did the Pilot's points not change? 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.
So, in our previous update example, we were modifying the Pilot child of a Car object. When we saved the change, we told db4o to save our Car object and asumed that the modified Pilot would be updated. But we were modifying and saving in the same manner as we were in the first update sample, so why did it work before? The first time we made the modification, db4o never actually had to retreive the modified Pilot it returned the same one that was still in memory that we modified, but it never actually updated the database. The fact that we saw the modified value was, in fact, a bug. Restarting the application would show that the value was unchanged.
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] Db4oFactory.Configure().ObjectClass(GetType(Car)).CascadeOnUpdate(True)`

`[updatePilotSeparateSessionsImprovedPart2] Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) found.Pilot.AddPoints(1) db.[Set](found)`

`[updatePilotSeparateSessionsImprovedPart3] Dim result As ObjectSet = 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] Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) db.Delete(found) result = db.[Get](New Car(Nothing)) ListResult(result)`

OUTPUT: 1 BMW[Rubens Barrichello/99]

Fine, the car is gone. What about the pilots?

`[retrieveAllPilotsQBE] Dim proto As Pilot = New Pilot(Nothing, 0) Dim result As ObjectSet = 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] Db4oFactory.Configure().ObjectClass(GetType(Car)).CascadeOnDelete(True)`

`[deleteDeepPart2] Dim result As ObjectSet = db.[Get](New Car("BMW")) Dim found As Car = DirectCast(result.[Next](), Car) db.Delete(found) result = db.[Get](New Car(Nothing)) ListResult(result)`

OUTPUT: 0

Again: Note that all configuration must take place before the ObjectContainer is opened.
Let's have a look at our pilots again.

`[retrieveAllPilots] Dim proto As Pilot = New Pilot(Nothing, 0) Dim result As ObjectSet = db.[Get](proto) ListResult(result)`

OUTPUT: 0

## 4.4.2. Recursive deletion revisited

But wait - what happens if the children of a removed object are still referenced by other objects?

`[deleteDeepRevisited] Dim result As ObjectSet = db.[Get](New Pilot("Michael Schumacher", 0)) Dim pilot As Pilot = DirectCast(result.[Next](), Pilot) Dim car1 As Car = New Car("Ferrari") Dim car2 As Car = New Car("BMW") car1.Pilot = pilot car2.Pilot = pilot db.[Set](car1) db.[Set](car2) db.Delete(car2) result = db.[Get](New Car(Nothing)) ListResult(result)`

OUTPUT: 1 Ferrari[null]

`[retrieveAllPilots] Dim proto As Pilot = New Pilot(Nothing, 0) Dim result As ObjectSet = db.[Get](proto) ListResult(result)`

OUTPUT: 0

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 using this feature.
Let's clear our database for the next chapter.

`[deleteAll] Dim result As ObjectSet = db.[Get](GetType(Object)) For Each item As Object In result db.Delete(item) 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

`Imports System Imports System.IO Imports com.db4o Imports com.db4o.query Namespace com.db4o.f1.chapter2 Public Class StructuredExample Inherits Util Public Shared Sub Main(ByVal args As String()) File.Delete(Util.YapFileName) Dim db As ObjectContainer = Db4oFactory.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 = Db4oFactory.OpenFile(Util.YapFileName) UpdatePilotSeparateSessionsPart2(db) db.Close() UpdatePilotSeparateSessionsImprovedPart1(db) db = Db4oFactory.OpenFile(Util.YapFileName) UpdatePilotSeparateSessionsImprovedPart2(db) db.Close() db = Db4oFactory.OpenFile(Util.YapFileName) UpdatePilotSeparateSessionsImprovedPart3(db) DeleteFlat(db) db.Close() DeleteDeepPart1(db) db = Db4oFactory.OpenFile(Util.YapFileName) DeleteDeepPart2(db) DeleteDeepRevisited(db) Finally db.Close() End Try End Sub Public Shared Sub StoreFirstCar(ByVal db As ObjectContainer) Dim car1 As Car = New Car("Ferrari") Dim pilot1 As Pilot = New Pilot("Michael Schumacher", 100) car1.Pilot = pilot1 db.[Set](car1) End Sub Public Shared Sub StoreSecondCar(ByVal db As ObjectContainer) Dim pilot2 As Pilot = New Pilot("Rubens Barrichello", 99) db.[Set](pilot2) Dim car2 As Car = New Car("BMW") car2.Pilot = pilot2 db.[Set](car2) End Sub Public Shared Sub RetrieveAllCarsQBE(ByVal db As ObjectContainer) Dim proto As Car = New Car(Nothing) Dim result As ObjectSet = db.[Get](proto) ListResult(result) End Sub Public Shared Sub RetrieveAllPilotsQBE(ByVal db As ObjectContainer) Dim proto As Pilot = New Pilot(Nothing, 0) Dim result As ObjectSet = db.[Get](proto) ListResult(result) End Sub Public Shared Sub RetrieveCarByPilotQBE(ByVal db As ObjectContainer) Dim pilotproto As Pilot = New Pilot("Rubens Barrichello", 0) Dim carproto As Car = New Car(Nothing) carproto.Pilot = pilotproto Dim result As ObjectSet = db.[Get](carproto) ListResult(result) End Sub Public Shared Sub RetrieveCarByPilotNameQuery(ByVal db As ObjectContainer) Dim query As Query = db.Query() query.Constrain(GetType(Car)) query.Descend("_pilot").Descend("_name").Constrain("Rubens Barrichello") Dim result As ObjectSet = query.Execute() ListResult(result) End Sub Public Shared Sub RetrieveCarByPilotProtoQuery(ByVal db As ObjectContainer) Dim query As Query = db.Query() query.Constrain(GetType(Car)) Dim proto As Pilot = New Pilot("Rubens Barrichello", 0) query.Descend("_pilot").Constrain(proto) Dim result As ObjectSet = query.Execute() ListResult(result) End Sub Public Shared Sub RetrievePilotByCarModelQuery(ByVal db As ObjectContainer) Dim carQuery As Query = db.Query() carQuery.Constrain(GetType(Car)) carQuery.Descend("_model").Constrain("Ferrari") Dim pilotQuery As Query = carQuery.Descend("_pilot") Dim result As ObjectSet = pilotQuery.Execute() ListResult(result) End Sub Public Shared Sub RetrieveAllPilots(ByVal db As ObjectContainer) Dim results As ObjectSet = db.[Get](GetType(Pilot)) ListResult(results) End Sub Public Shared Sub RetrieveAllCars(ByVal db As ObjectContainer) Dim results As ObjectSet = db.[Get](GetType(Car)) ListResult(results) End Sub Public Class RetrieveCarsByPilotNamePredicate Inherits Predicate ReadOnly _pilotName As String Public Sub New(ByVal pilotName As String) _pilotName = pilotName End Sub Public Function Match(ByVal candidate As Car) As Boolean Return candidate.Pilot.Name = _pilotName End Function End Class Public Shared Sub RetrieveCarsByPilotNameNative(ByVal db As ObjectContainer) Dim pilotName As String = "Rubens Barrichello" Dim results As ObjectSet = db.Query(New RetrieveCarsByPilotNamePredicate(pilotName)) ListResult(results) End Sub Public Shared Sub UpdateCar(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) found.Pilot = New Pilot("Somebody else", 0) db.[Set](found) result = db.[Get](New Car("Ferrari")) ListResult(result) End Sub Public Shared Sub UpdatePilotSingleSession(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) found.Pilot.AddPoints(1) db.[Set](found) result = db.[Get](New Car("Ferrari")) ListResult(result) End Sub Public Shared Sub UpdatePilotSeparateSessionsPart1(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) found.Pilot.AddPoints(1) db.[Set](found) End Sub Public Shared Sub UpdatePilotSeparateSessionsPart2(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("Ferrari")) ListResult(result) End Sub Public Shared Sub UpdatePilotSeparateSessionsImprovedPart1(ByVal db As ObjectContainer) Db4oFactory.Configure().ObjectClass(GetType(Car)).CascadeOnUpdate(True) End Sub Public Shared Sub UpdatePilotSeparateSessionsImprovedPart2(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) found.Pilot.AddPoints(1) db.[Set](found) End Sub Public Shared Sub UpdatePilotSeparateSessionsImprovedPart3(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("Ferrari")) ListResult(result) End Sub Public Shared Sub DeleteFlat(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("Ferrari")) Dim found As Car = DirectCast(result.[Next](), Car) db.Delete(found) result = db.[Get](New Car(Nothing)) ListResult(result) End Sub Public Shared Sub DeleteDeepPart1(ByVal db As ObjectContainer) Db4oFactory.Configure().ObjectClass(GetType(Car)).CascadeOnDelete(True) End Sub Public Shared Sub DeleteDeepPart2(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Car("BMW")) Dim found As Car = DirectCast(result.[Next](), Car) db.Delete(found) result = db.[Get](New Car(Nothing)) ListResult(result) End Sub Public Shared Sub DeleteDeepRevisited(ByVal db As ObjectContainer) Dim result As ObjectSet = db.[Get](New Pilot("Michael Schumacher", 0)) Dim pilot As Pilot = DirectCast(result.[Next](), Pilot) Dim car1 As Car = New Car("Ferrari") Dim car2 As Car = New Car("BMW") car1.Pilot = pilot car2.Pilot = pilot db.[Set](car1) db.[Set](car2) db.Delete(car2) result = db.[Get](New Car(Nothing)) ListResult(result) End Sub End Class End Namespace`

--
generated by Doctor courtesy of db4objects Inc.