|
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) |
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) |
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) |
[retrieveAllPilots]
Dim proto As Pilot = New Pilot(Nothing, 0)
Dim result As ObjectSet = 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 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.