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)