Keys
Keys overview
Key represents a set of identity fields. Every hierarchy defines its own identity structure and every entity is identified by key. Number of fields in key is limited to 8. DataObjects.Net supports the following .NET types to be used in identity fields:
boolean;byte, sbyte, short, ushort, int, uint, long, ulong;string, char;double, float, decimal;Guid, DateTime, TimeSpan;Reference to
Entity
Usage of Structure and EntitySet<T> types is not allowed.
Number and types of identity fields are set once for all persistent
hierarchy, so all descendants of hierarchy root share the key structure.
In order to set up identity fields, KeyAttribute class should be
used:
[HierarchyRoot]
public class Document : Entity
{
  [Key, Field]
  public int Id { get; private set; }
  ...
}
KeyAttribute must be placed on each identity field. Also note, that
identity field must be immutable, it is prohibited for identity field to
have a setter other than private.
For persistent types with complex keys (which have more than one identity field) explicit identity field order is strictly recommended, because the particular order of list of properties, got with the help of .NET reflection, is not guaranteed at all.
[HierarchyRoot]
public class BookReview : Entity
{
  [Field, Key(0)]
  public Book Book { get; private set; }
  [Field, Key(1)]
  public Person Reviewer { get; private set; }
  [Field(Length = 4096)]
  public string Text { get; set; }
  public BookReview(Session session, Book book, Person reviewer)
    : base(session, book, reviewer)
  {}
}
Pay attention to KeyAttribute usage. In this example it also used to
set the position of identity field within complex key. So for
BookReview type the structure of key is {Book, Person}. Also
note that values for both identity fields are required in
BookReview constructor and passed to the base constructor of
Entity where actual key is constructed.
Working with keys
Creating keys
Creating the next unique key in corresponding key sequence for the specified persistent type. This can be done by these group of static methods:
Key.Generate<Document>(Session);
// or
Key.Generate(Session, typeof (Document));
Note that methods don’t receive any arguments except Session &
Type. This means that we ask to generate the next unique key in key
sequence.
It is also worth noting that generation of next key in sequence requires
an instanse of Session because DataObjects.Net might need to connect
to underlying storage and call the corresponding key generator for a
next key in sequence.
For example, to fetch an entity from storage we need a key that identifies it. In this case we need to construct a key with already known value(s).
// Active session is not required
Key.Create<Document>(Domain, params object[] values);
Key.Create(Domain, typeof (Document), params object[] values);
If we want to build a key for an instance of Document class with
identifier equals to 25 we write something like this:
var key = Key.Create<Document>(Domain, 25);
Note that presence of Session is not required as we don’t need a
connection to underlying storage at all.
There is no public constructor in Key class. The reason for this and
the usage of Factory method pattern instead of constructors is that we
have several Key implementations, 4 of them are designed and
extremely optimized for short keys (Key<T1>, Key<T1,T2>,
Key<T1,T2,T3>, Key<T1,T2,T3,T4>) and one is for keys with length
up to 8 fields (LongKey).
Serializing and deserializing keys
Keys can be automatically serialized into the corresponding string
representation with the help of Key.Format() method and deserialized
from the string using static Key.Parse() method.
var key = Key.Create<Document>(25);
var str = key.Format();
Console.WriteLine(str);
// This will print: "103:25"
// where 103 is identifier of Document type in Domain model
// and 25 is the value of identifier field.
var key2 = Key.Parse(Domain, str);
Assert.AreEqual(key, key2);
Key generators
Where keys come from?
Every entity is uniquely identified by key and Entity.Key property
is immutable during whole entity’s lifecycle. The only moment when
Entity.Key property can be set is the moment of entity construction.
There are two scenarios and DataObjects.Net supports both:
1. Values of identity fields are provided by user code (not by framework). User code is responsible for passing identity values directly to entity constructor where these values are automatically transformed into key.
[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.None)]
public class Book : Entity
{
  [Field, Key]
  public string ISBN { get; private set; }
  // Accepts identity value (ISBN) and passes it to the base Entity constructor
  public Book(Session session, string isbn)
    : base(session, isbn)
  {
  }
}
2. Values of identity fields are provided by framework. This scenario might involve usage of database identity generators, tables with auto-increment column as well as any custom identity generators in order to produce new identity values for entity and build its key.
[HierarchyRoot]
public class Book : Entity
{
  [Field, Key]
  public int Id { get; private set; }
  public Book(Session session)
    : base(session)
  {
  }
}
While the first scenario is straighforward and doesn’t need any framework participation at all (except creating key from values passed to constructor), the second one is not so obvious because it requires framework to know how to obtain the next unique identity values for the specified persistent type. To solve this task the concept of key generator was introduced in DataObjects.Net.
Standard key generators
Key generator is a special object that knows all necessary information concerning generation of unique keys for particular hierarchy of persistent types, including:
details of identity generator implementation;
structure of key;
size of key cache, if generator supports caching;
mapping name, if generator is mapped to a particular database entity;
etc.
DataObjects.Net contains key generator implementations for the following types of identity fields:
byte, sbyte, short, ushort, int, uint, long, ulong;string, Guid;
They can be used for persistent types with one identity field. This
means they are not suitable for persistent types with complex key (more
than one identity field). Sequential key generators (for integer
types) also support caching.
Depending on features of the underlying storage, sequential key
generator is mapped to sequence or a standalone table with one
autoincrement column. Usually, the name of such physical entity is
produced according to the rule ‘{type}-Generator’. For example, sequence
for key generator of Int32 type will be ‘Int32-Generator’.
Dedicated key generators
Dedicated key generators are the opposite case to shared generators. Having dedicated key generators, you get separate sequence for a given hierarchy.
[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.Default, Name = "BookGenerator")]
public class Book : Entity
{
  [Field, Key]
  public int Id { get; private set; }
  public Book(Session session)
    : base(session)
  {
  }
}
[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.Default, Name = "AuthorGenerator")]
public class Author : Entity
{
  [Field, Key]
  public int Id { get; private set; }
  public Author(Session session)
    : base(session)
  {
  }
}
In this example we indicate, we want to have 2 separate key generators: ‘BookGenerator’ and ‘AuthorGenerator’. DataObjects.Net will create them as two standalone sequences or tables with autoincrement field. Each hierarchy will be served by dedicated key generator with the corresponding name.
Hierarchies without key generator
In scenarios when key generator for a hierarchy is not required at all, this must be set up appropriately:
[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.None)]
public class Book : Entity
{
  [Field, Key]
  public string ISBN { get; private set; }
  // Accepts identity value (ISBN) and passes it to the base Entity constructor
  public Book(Session session, string isbn)
    : base(session, isbn) { }
}
Pay attention to KeyGeneratorAttribute usage together with identity
values that are provided from the outside and passed to constructor.
Custom key generators
DataObjects.Net supports wide variety of keys and provides built-in key
generator implementation only for keys that contain one identity field.
For other scenarios that require key generator it is suggested to define
custom key generator, inheriting from KeyGenerator type and
implement a pair of its methods.
Here is the sample implementation of key generator for one-column
Guid key:
[Service(typeof (KeyGenerator), Name = "MyGuidKeyGenerator")]
public class MyGuidKeyGenerator : KeyGenerator
{
  public override void Initialize(Domain domain, TupleDescriptor keyDescriptor)
  {
    // Key generator initialization logic, if any.
    // Executed on Domain.Build() step
  }
  public override Tuple GenerateKey(KeyInfo keyInfo, Session session)
  {
    // Creating tuple for key
    var keyTuple = Tuple.Create(keyInfo.TupleDescriptor);
    // Setting the first column of tuple with generated guid value
    keyTuple.SetValue(0, Guid.NewGuid());
    return keyTuple;
  }
}
The name that set in ServiceAttribute is uniquelly identifies custom
key generator.
In order to indicate that a hierarchy must be served with custom key
generator, use KeyGeneratorAttribute. Sample usage:
[HierarchyRoot]
[KeyGenerator(KeyGeneratorKind.Custom, Name = "MyGuidKeyGenerator")]
public class Author : Entity
{
  [Field, Key]
  public Guid Id { get; private set; }
  [Field]
  public EntitySet<Book> Books { get; private set; }
}
Custom generators are found by their names, so it is necessary to set
the KeyGeneratorAttribute.Name property with correct name of key
gerenator you defined earlier.