|
7. Deep graphs
We have already seen how db4o handles object associations, but our running example is still quite flat and simple, compared to real-world domain models. In particular we haven't seen how db4o behaves in the presence of recursive structures. We will emulate such a structure by replacing our history list with a linked list implicitely provided by the SensorReadout class.
Imports System
Namespace com.db4o.f1.chapter5
Public Class SensorReadout
Private _time As DateTime
Private _car As Car
Private _description As String
Private _next As SensorReadout
Protected Sub New(ByVal time As DateTime, ByVal car As Car, ByVal description As String)
_time = time
_car = car
_description = description
_next = Nothing
End Sub
Public ReadOnly Property Car() As Car
Get
Return _car
End Get
End Property
Public ReadOnly Property Time() As DateTime
Get
Return _time
End Get
End Property
Public ReadOnly Property [Next]() As SensorReadout
Get
Return _next
End Get
End Property
Public Sub Append(ByVal sensorReadout As SensorReadout)
If _next Is Nothing Then
_next = sensorReadout
Else
_next.Append(sensorReadout)
End If
End Sub
Public Function CountElements() As Integer
If _next Is Nothing Then
Return 1
End If
Return _next.CountElements() + 1
End Function
Public Overloads Overrides Function ToString() As String
Return String.Format("{0} : {1} : {2}", _car, _time, _description)
End Function
End Class
End Namespace
|
Our car only maintains an association to a 'head' sensor readout now.
Imports System
Namespace com.db4o.f1.chapter5
Public Class Car
Private _model As String
Private _pilot As Pilot
Private _history As SensorReadout
Public Sub New(ByVal model As String)
_model = model
_pilot = Nothing
_history = 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 Function GetHistory() As SensorReadout
Return _history
End Function
Public Sub Snapshot()
AppendToHistory(New TemperatureSensorReadout(DateTime.Now, Me, "oil", PollOilTemperature()))
AppendToHistory(New TemperatureSensorReadout(DateTime.Now, Me, "water", PollWaterTemperature()))
AppendToHistory(New PressureSensorReadout(DateTime.Now, Me, "oil", PollOilPressure()))
End Sub
Protected Function PollOilTemperature() As Double
Return 0.1 * CountHistoryElements()
End Function
Protected Function PollWaterTemperature() As Double
Return 0.2 * CountHistoryElements()
End Function
Protected Function PollOilPressure() As Double
Return 0.3 * CountHistoryElements()
End Function
Public Overloads Overrides Function ToString() As String
Return String.Format("{0}[{1}]/{2}", _model, _pilot, CountHistoryElements())
End Function
Private Function CountHistoryElements() As Integer
If _history Is Nothing Then
Return 0
End If
Return _history.CountElements()
End Function
Private Sub AppendToHistory(ByVal readout As SensorReadout)
If _history Is Nothing Then
_history = readout
Else
_history.Append(readout)
End If
End Sub
End Class
End Namespace
|
7.1. Storing and updating
No surprises here.
[storeCar]
Dim pilot As Pilot = New Pilot("Rubens Barrichello", 99)
Dim car As Car = New Car("BMW")
car.Pilot = pilot
db.[Set](car) |
Now we would like to build a sensor readout chain. We already know about the update depth trap, so we configure this first.
[setCascadeOnUpdate]
Db4oFactory.Configure().ObjectClass(GetType(Car)).CascadeOnUpdate(True) |
Let's collect a few sensor readouts.
[takeManySnapshots]
Dim result As ObjectSet = db.[Get](GetType(Car))
Dim car As Car = DirectCast(result.[Next](), Car)
Dim i As Integer = 0
While i < 5
car.Snapshot()
System.Math.Max(System.Threading.Interlocked.Increment(i),i - 1)
End While
db.[Set](car) |
7.2. Retrieving
Now that we have a sufficiently deep structure, we'll retrieve it from the database and traverse it.
First let's verify that we indeed have taken lots of snapshots.
[retrieveAllSnapshots]
Dim result As ObjectSet = db.[Get](GetType(SensorReadout))
While result.HasNext()
Console.WriteLine(result.[Next]())
End While |
OUTPUT: BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 4.2
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 1.2000000000000002
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 3.3
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.0
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.9
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 2.4
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.6000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 0.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 1.4000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 1.5
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.8
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.30000000000000004
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.2
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.0
|
|
All these readouts belong to one linked list, so we should be able to access them all by just traversing our list structure.
[retrieveSnapshotsSequentially]
Dim result As ObjectSet = db.[Get](GetType(Car))
Dim car As Car = DirectCast(result.[Next](), Car)
Dim readout As SensorReadout = car.GetHistory()
While Not readout Is Nothing
Console.WriteLine(readout)
readout = readout.[Next]
End While |
OUTPUT: BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.0
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.2
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 0.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.30000000000000004
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.8
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 1.5
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.6000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 1.4000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 2.4
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.9
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.0
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 3.3
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 1.2000000000000002
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 4.2
|
Ouch! What's happening here?
7.2.1. Activation depth
Deja vu - this is just the other side of the update depth issue.
db4o cannot track when you are traversing references from objects retrieved from the database. So it would always have to return 'complete' object graphs on retrieval - in the worst case this would boil down to pulling the whole database content into memory for a single query.
This is absolutely undesirable in most situations, so db4o provides a mechanism to give the client fine-grained control over how much he wants to pull out of the database when asking for an object. This mechanism is called activation depthand works quite similar to our familiar update depth.
The default activation depth for any object is 5, so our example above runs into nulls after traversing 5 references.
We can dynamically ask objects to activate their member references. This allows us to retrieve each single sensor readout in the list from the database just as needed.
[retrieveSnapshotsSequentiallyImproved]
Dim result As ObjectSet = db.[Get](GetType(Car))
Dim car As Car = DirectCast(result.[Next](), Car)
Dim readout As SensorReadout = car.GetHistory()
While Not readout Is Nothing
db.Activate(readout, 1)
Console.WriteLine(readout)
readout = readout.[Next]
End While |
OUTPUT: BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.0
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.2
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 0.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.30000000000000004
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.8
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 1.5
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.6000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 1.4000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 2.4
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.9
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.0
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 3.3
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 1.2000000000000002
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 4.2
|
Note that 'cut' references may also influence the behavior of your objects: In this case the length of the list is calculated dynamically, and therefor constrained by activation depth.
Instead of dynamically activating subgraph elements, you can configure activation depth statically, too. We can tell our SensorReadout class objects to cascade activation automatically, for example.
[setActivationDepth]
Db4oFactory.Configure().ObjectClass(GetType(TemperatureSensorReadout)).CascadeOnActivate(True) |
[retrieveSnapshotsSequentially]
Dim result As ObjectSet = db.[Get](GetType(Car))
Dim car As Car = DirectCast(result.[Next](), Car)
Dim readout As SensorReadout = car.GetHistory()
While Not readout Is Nothing
Console.WriteLine(readout)
readout = readout.[Next]
End While |
OUTPUT: BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.0
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.2
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 0.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.30000000000000004
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 0.8
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 1.5
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.6000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 1.4000000000000001
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 2.4
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 0.9
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.0
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 3.3
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil temp : 1.2000000000000002
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : water temp : 2.6
BMW[Rubens Barrichello/99]/15 : Wed Jul 05 10:33:49 CEST 2006 : oil pressure : 4.2
|
You have to be very careful, though. Activation issues are tricky. Db4o provides a wide range of configuration features to control activation depth at a very fine-grained level. You'll find those triggers in com.db4o.config.Configuration and the associated ObjectClass and ObjectField classes.
Don't forget to clean up the database.
[deleteAll]
Dim result As ObjectSet = db.[Get](GetType(Object))
For Each item As Object In result
db.Delete(item)
Next |
7.3. Conclusion
Now we should have the tools at hand to work with arbitrarily complex object graphs. But so far we have only been working forward, hoping that the changes we apply to our precious data pool are correct. What if we have to roll back to a previous state due to some failure? In the
next chapter we will introduce the db4o transaction concept.
7.4. Full source
Imports System
Imports System.IO
Imports com.db4o
Namespace com.db4o.f1.chapter5
Public Class DeepExample
Inherits Util
Public Shared Sub Main(ByVal args As String())
File.Delete(Util.YapFileName)
Dim db As ObjectContainer = Db4oFactory.OpenFile(Util.YapFileName)
Try
StoreCar(db)
db.Close()
SetCascadeOnUpdate()
db = Db4oFactory.OpenFile(Util.YapFileName)
TakeManySnapshots(db)
db.Close()
db = Db4oFactory.OpenFile(Util.YapFileName)
RetrieveAllSnapshots(db)
db.Close()
db = Db4oFactory.OpenFile(Util.YapFileName)
RetrieveSnapshotsSequentially(db)
RetrieveSnapshotsSequentiallyImproved(db)
db.Close()
SetActivationDepth()
db = Db4oFactory.OpenFile(Util.YapFileName)
RetrieveSnapshotsSequentially(db)
Finally
db.Close()
End Try
End Sub
Public Shared Sub StoreCar(ByVal db As ObjectContainer)
Dim pilot As Pilot = New Pilot("Rubens Barrichello", 99)
Dim car As Car = New Car("BMW")
car.Pilot = pilot
db.[Set](car)
End Sub
Public Shared Sub SetCascadeOnUpdate()
Db4oFactory.Configure().ObjectClass(GetType(Car)).CascadeOnUpdate(True)
End Sub
Public Shared Sub TakeManySnapshots(ByVal db As ObjectContainer)
Dim result As ObjectSet = db.[Get](GetType(Car))
Dim car As Car = DirectCast(result.[Next](), Car)
Dim i As Integer = 0
While i < 5
car.Snapshot()
System.Math.Max(System.Threading.Interlocked.Increment(i),i - 1)
End While
db.[Set](car)
End Sub
Public Shared Sub RetrieveAllSnapshots(ByVal db As ObjectContainer)
Dim result As ObjectSet = db.[Get](GetType(SensorReadout))
While result.HasNext()
Console.WriteLine(result.[Next]())
End While
End Sub
Public Shared Sub RetrieveSnapshotsSequentially(ByVal db As ObjectContainer)
Dim result As ObjectSet = db.[Get](GetType(Car))
Dim car As Car = DirectCast(result.[Next](), Car)
Dim readout As SensorReadout = car.GetHistory()
While Not readout Is Nothing
Console.WriteLine(readout)
readout = readout.[Next]
End While
End Sub
Public Shared Sub RetrieveSnapshotsSequentiallyImproved(ByVal db As ObjectContainer)
Dim result As ObjectSet = db.[Get](GetType(Car))
Dim car As Car = DirectCast(result.[Next](), Car)
Dim readout As SensorReadout = car.GetHistory()
While Not readout Is Nothing
db.Activate(readout, 1)
Console.WriteLine(readout)
readout = readout.[Next]
End While
End Sub
Public Shared Sub SetActivationDepth()
Db4oFactory.Configure().ObjectClass(GetType(TemperatureSensorReadout)).CascadeOnActivate(True)
End Sub
End Class
End Namespace
|
--
generated by Doctor courtesy of db4objects Inc.