Skip to content

Primary Key Handlers

Shannon Lewis edited this page Jul 7, 2021 · 1 revision

Primary Key Handlers are used to map the Id column to/from the database.

A key handler has the following responsibilities:

  • Identity which CLR type it is designed to handle
  • provide the SqlMetaData required to build TableValueParameters
  • work with a KeyAllocator to get the next key for a given table (used during Inserts)
  • convert Id values to their CLR primitive value (used during Inserts and Deletes)

This last point is to cover cases where the models are not using raw primitive types as their Id property. For example, they may be wrapping a primitive in a custom type for type safety, so CustomerId can't be confused with OrderId even though they both represent a string or an int.

Implementing a custom primary key handler

Below is an example of how you might implement a custom Primary Key Handler for a customer table, if the document had an Id property of type CustomerId.

public class CustomerId
{
    public int Value { get;set; }
}

public class CustomerPrimaryKeyHandler : IPrimaryKeyHandler
{
    public Type Type => typeof(CustomerId);

    public SqlMetaData GetSqlMetaData(string name)
    {
        return new SqlMetaData(name, SqlDbType.Int);
    }

    [return: NotNullIfNotNull("id")]
    public virtual object? ConvertToPrimitiveValue(object? id)
    {
        return (CustomerId)id.Value;
    }

    public object GetNextKey(IKeyAllocator keyAllocator, string tableName)
    {
        return new CustomerId { Value = keyAllocator.NextId(tableName) };
    }
}

The key concepts here are that:

  • the GetNextKey should always return a value that is the actual CLR type of the document's Id property
  • Nevermore will source a value from either the Id property or GetNextKey, depending on which operation it is performing, and will use the ConvertToPrimitiveValue to convert back to the primitive value required by a SqlCommandParameter.

Registering a custom primary key handler

There are two options available for configuring primary key handlers. One is to register them for a specific table, in its DocumentMap.

    Id().KeyHandler(new CustomerPrimaryKeyHandler());

The second is to register it globally in the store's configuration.

store.Configuration.PrimaryKeyHandlers.Register(new CustomerPrimaryKeyHandler());

It will than apply automatically to any DocumentMap that has a document type with an Id of that type.

ℹ️ Tip:

There are 4 built in primary key handlers. One each for string, int, long, and Guid. You can override any of the default implementations by implementing your own IPrimaryKeyHandler of any of these types and registering it. Per DocumentMap or globally through the store's configuration can both be used to do the override.