Type-specific identifier generation
In the previous article, global key generation conventions were introduced. Any customization made by using those conventions changes the behavior for all stored entities. Now we will show how to override the default ID generation in a more granular way, that is only for particular types of entities.
To override default document key generation algorithms you can register custom conventions per an entity type, where you can include your own identifier generation logic. There are two methods to do so:
RegisterIdConvention
DocumentConvention RegisterIdConvention<TEntity>(Func<string, IDatabaseCommands, TEntity, string> func);
The conventions registered by this method are used for operations performed in a synchronous manner.
Parameters | ||
---|---|---|
func | Func<string, IDatabaseCommands, TEntity, string> | Identifier generation function for the given database name (string ), commands object (IDatabaseCommands ) and entity object (TEntity ). |
Return Value | |
---|---|
DocumentConvention | Current DocumentConvention instance. |
Type: DocumentConvention
Current DocumentConvention
instance.
RegisterAsyncIdConvention
DocumentConvention RegisterAsyncIdConvention<TEntity>(Func<string, IAsyncDatabaseCommands, TEntity,
Task<string>> func);
The conventions registered by this method are used for operations performed in an asynchronous manner.
Parameters | ||
---|---|---|
func | Func<string, IAsyncDatabaseCommands, TEntity, Task<string>> | Identifier generation function that supplies a result in async way for given database name (string ), async commands object (IAsyncDatabaseCommands ) and entity object (TEntity ). |
Return Value | |
---|---|
DocumentConvention | Current DocumentConvention instance. |
Database name parameter
The database name parameter is passed to the register convention methods to allow users to make Id generation decision per database
(e.g. default document key generator - MultiDatabaseHiloGenerator
- uses this parameter to prevent sharing HiLo values across the databases)
Database commands parameter
Note that spectrum of identifier generation abilities is very wide because IDatabaseCommands
(or IAsyncDatabaseCommands
) object is passed into an identifier convention function and can be used for an advanced calculation techniques.
Example
Let's say that you want to use semantic identifiers for Employee
objects. Instead of employee/[identity]
you want to have keys like employees/[lastName]/[firstName]
(for the sake of simplicity, let us not consider the uniqueness of such keys). What you need to do is to create the convention that will combine the employee
prefix, LastName
and FirstName
properties of an employee.
store.Conventions.RegisterIdConvention<Employee>(
(dbname, commands, employee) => string.Format("employees/{0}/{1}", employee.LastName, employee.FirstName));
If you want to register your convention for async operations, use the second method:
store.Conventions.RegisterAsyncIdConvention<Employee>(
(dbname, commands, employee) => new CompletedTask<string>(
string.Format("employees/{0}/{1}", employee.LastName, employee.FirstName)));
Now, when you store a new entity:
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Employee
{
FirstName = "James",
LastName = "Bond"
});
session.SaveChanges();
}
the client will associate the employees/Bond/James
identifier with it.
Inheritance
Registered conventions are inheritance-aware, so all types that can be assigned from registered type will fall into that convention according to inheritance-hierarchy tree.
Example
If we create a new class EmployeeManager
that will derive from our Employee
class and keep the convention registered in the last example, then both types will use the following:
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Employee // employees/Smith/Adam
{
FirstName = "Adam",
LastName = "Smith"
});
session.Store(new EmployeeManager // employees/Jones/David
{
FirstName = "David",
LastName = "Jones"
});
session.SaveChanges();
}
If we register two conventions, one for Employee
and the second for EmployeeManager
then they will be picked for their specific types.
store.Conventions.RegisterIdConvention<Employee>(
(dbname, commands, employee) => string.Format("employees/{0}/{1}", employee.LastName, employee.FirstName));
store.Conventions.RegisterIdConvention<EmployeeManager>(
(dbname, commands, employee) => string.Format("managers/{0}/{1}", employee.LastName, employee.FirstName));
using (IDocumentSession session = store.OpenSession())
{
session.Store(new Employee // employees/Smith/Adam
{
FirstName = "Adam",
LastName = "Smith"
});
session.Store(new EmployeeManager // managers/Jones/David
{
FirstName = "David",
LastName = "Jones"
});
session.SaveChanges();
}
Loading entities with customized IDs by non string identifiers
The RavenDB client supports identifiers that aren't strings. There is a dedicated overload of Load
method which accepts value types to handle that.
However if you decide to register a custom convention for an entity that has non string id then you will experience problems with the usage of this Load<T>(ValueType)
method overload because
by default such call will try to load a document with the key collectionNameBasedOnType/valueTypeValue
.
In order to handle such case you need to use the following convention:
DocumentConvention RegisterIdLoadConvention<TEntity>(Func<ValueType, string> func);
Parameters | ||
---|---|---|
func | Func<ValueType, string> | Function that transforms the value type identifier provided into Load<TEntity>(ValueType) . It has to be consistent with the registered convention for the type TEntity . |
Return Value | |
---|---|
DocumentConvention | Current DocumentConvention instance. |
Example
If you have an entity where the identifier is not a string:
public class EntityWithIntegerId
{
public int Id { get; set; }
/*
...
*/
}
and you registered the custom id conventions for it:
store.Conventions.RegisterIdConvention<EntityWithIntegerId>(
(databaseName, commands, entity) => "ewi/" + entity.Id);
then you need to register the same customization by using RegisterIdLoadConvention
:
store.Conventions.RegisterIdLoadConvention<EntityWithIntegerId>(id => "ewi/" + id);
using (IDocumentSession session = store.OpenSession())
{
EntityWithIntegerId entity = session.Load<EntityWithIntegerId>(1); // will load 'ewi/1' document
}