db4o does not deliver a field auto increment feature, which
is common in RDBMS. Normally you don't need any additional ids, since db4o manages objects by object-identity. However cases where you have disconnected objects, you need additional ids. One of then possibilities it to use auto incremented ids.
If your application logic requires this feature you can implement it using external callbacks. One of the possible solutions is presented below. Note that this example only works in embedded-mode.
This example assumes that all object which need an auto incremented id are subclasses of the IDHolder-class. This class contains the auto-incremented id.
private int id; public int Id { get { return id; } set { id = value; } }
Private m_id As Integer Public Property Id() As Integer Get Return m_id End Get Set(ByVal value As Integer) m_id = value End Set End Property
First create a class which keeps the state of the auto-increment numbers. For example a map which keeps the latest auto incremented id for each class.
private class PersistedAutoIncrements { private readonly IDictionary<Type, int> currentHighestIds = new Dictionary<Type, int>(); public int NextNumber(Type forClass) { int number; if (!currentHighestIds.TryGetValue(forClass, out number)) { number = 0; } number += 1; currentHighestIds[forClass] = number; return number; } }
Private Class PersistedAutoIncrements Private ReadOnly currentHighestIds As IDictionary(Of Type, Integer) _ = New Dictionary(Of Type, Integer)() Public Function NextNumber(ByVal forClass As Type) As Integer Dim number As Integer If Not currentHighestIds.TryGetValue(forClass, number) Then number = 0 End If number += 1 currentHighestIds(forClass) = number Return number End Function End Class
Then create two methods, which are called later. One which returns the next auto-incremented id for a certain class. Another which stores the current state of the auto-increments.
public int GetNextID(Type forClass) { lock (dataLock) { PersistedAutoIncrements incrementState = EnsureLoadedIncrements(); return incrementState.NextNumber(forClass); } } public void StoreState() { lock (dataLock) { if (null != state) { container.Ext().Store(state,2); } } }
Public Function GetNextID(ByVal forClass As Type) As Integer SyncLock dataLock Dim incrementState As PersistedAutoIncrements = EnsureLoadedIncrements() Return incrementState.NextNumber(forClass) End SyncLock End Function Public Sub StoreState() SyncLock dataLock If state IsNot Nothing Then container.Ext().Store(state, 2) End If End SyncLock End Sub
The last part is to ensure that the existing auto-increments are loaded from the database. Or if not existing a new instance is created.
private PersistedAutoIncrements EnsureLoadedIncrements() { if (null == state) { state = LoadOrCreateState(); } return state; } private PersistedAutoIncrements LoadOrCreateState() { IList<PersistedAutoIncrements> existingState = container.Query<PersistedAutoIncrements>(); if (0 == existingState.Count) { return new PersistedAutoIncrements(); } else if (1 == existingState.Count) { return existingState[0]; } else { throw new InvalidOperationException("Cannot have more than one state stored in database"); } }
Private Function EnsureLoadedIncrements() As PersistedAutoIncrements If state Is Nothing Then state = LoadOrCreateState() End If Return state End Function Private Function LoadOrCreateState() As PersistedAutoIncrements Dim existingState As IList(Of PersistedAutoIncrements) = container.Query(Of PersistedAutoIncrements)() If 0 = existingState.Count Then Return New PersistedAutoIncrements() ElseIf 1 = existingState.Count Then Return existingState(0) Else Throw New InvalidOperationException("Cannot have more than one state stored in database") End If End Function
Now it's time to use the callbacks. Every time when a new object is created, assign a new id. For this the creating-event is perfect. When commiting also make the auto increment-state persistent, to ensure that no id is used twice.
AutoIncrement increment = new AutoIncrement(container); IEventRegistry eventRegistry = EventRegistryFactory.ForObjectContainer(container); eventRegistry.Creating+= delegate(object sender, CancellableObjectEventArgs args) { if (args.Object is IDHolder) { IDHolder idHolder = (IDHolder)args.Object; idHolder.Id = increment.GetNextID(idHolder.GetType()); } }; eventRegistry.Committing += delegate(object sender, CommitEventArgs args) { increment.StoreState(); };
Dim increment As New AutoIncrement(container) Dim eventRegistry As IEventRegistry = EventRegistryFactory.ForObjectContainer(container) AddHandler eventRegistry.Creating, AddressOf increment.HandleCreating AddHandler eventRegistry.Committing, AddressOf increment.HandleCommiting
Last, don't forget to index the id-field. Otherwise looks-ups will be slow.
configuration.Common.ObjectClass(typeof (IDHolder)).ObjectField("id").Indexed(true);
configuration.Common.ObjectClass(GetType(IDHolder)).ObjectField("id").Indexed(True)