You are here: Advanced Features > Callbacks > Possible Usecases > Autoincrement

Autoincrement

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; }
}
IDHolder.cs: id holder
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
IDHolder.vb: id holder

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;
    }
}
AutoIncrement.cs: persistent auto increment
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
AutoIncrement.vb: persistent auto increment

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);
        }
    }
}
AutoIncrement.cs: getting the next id and storing state
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
AutoIncrement.vb: getting the next id and storing state

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");
    }
}
AutoIncrement.cs: load the state from the 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

AutoIncrement.vb: load the state from the database

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();    
        };
AutoIncrementExample.cs: use events to assign the ids
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
AutoIncrementExample.vb: use events to assign the ids

Last, don't forget to index the id-field. Otherwise looks-ups will be slow.

configuration.Common.ObjectClass(typeof (IDHolder)).ObjectField("id").Indexed(true);
AutoIncrementExample.cs: index the id-field
configuration.Common.ObjectClass(GetType(IDHolder)).ObjectField("id").Indexed(True)
AutoIncrementExample.vb: index the id-field