Disclaimer(s) :-)
  • Sto studiando quindi prendete tutto molto con le pinze;
  • Sono decisamente allergico al manuale delle istruzioni quindi usate delle pinze belle grosse :-);
Detto questo, immaginate di avere il vostro bel dominio siffatto:
image
Il resto per ora è poco importante, il capo dice che in DDD non è buona cosa esporre la Primary Key perchè non è cosa pertinente al dominio ma è un problema dello storage, sono pienamente d’accordo ma è altrettanto vero che è pur sempre un gran comodo averla li perchè un bel DataContext.GetById(…) non si nega a nessuno :-)
Detto questo è pur vero che potrebbe essere un gran comodo “mascherarla” del resto al dominio e all’applicazione non frega un bel nulla se è un Guid o un Int32:
IUniqueEntity
public interface IUniqueEntity 
{
    IKey Key { get; }
}
dove IKey è definita così:
public interface IKey : IEquatable<IKey>, IComparable
{
    Boolean IsEmpty { get; }
}
L’implementazione è del tutto irrilevante per il post quindi passo oltre, quello che però è importante capire è che alla fine della fiera possiamo fare “da qualche parte” qualcosa del tipo:
var guid = Guid.NewGuid();
IKey key = new Key<Guid>( guid );
e avere in mano la nostra bella chiave generica, adesso immaginiamo di dover mappare quel dominio al fine di utilizzarlo con NHibernate; utilizzando il fidissimo Fluent Mapping potete scrivere una cosa del genere:
public class SubjectMapping : ClassMap<Subject>
{
    public SubjectMapping()
    {
        this.WithTable( "Subjects" );

        this.Id( s => s.Key )
            .ColumnName( "Id" )
            .WithUnsavedValue( ??? )
            .GeneratedBy. ???
ok, ma che ci metttete al posto dei fatidici “Question Marks”?
IUserType
la soluzione, decisamente semplice è definire una classe, che tralascio perchè su google ne trovate a centinaia di esempi (uno su tutto questo) che sappia fare il mapping tra il tipo che NHibernate troverà a runtime nel db e il vostro tipo; fatto questo potete finalmente scrivere:
this.Id( s => s.Key )
    .ColumnName( "Id" )
    .WithUnsavedValue( null )
    .SetAttribute( "type", typeof( GuidKeyUserType ).AssemblyQualifiedName );
Utilizzando SetAttribute impostate il tipo custom, che deve implementare IUserType. Siccome la IKey è un reference type potete utilizzare come UnsavedValue null, bruttissimo :-) ma siate pazienti; è evidente che l’uso di null per gestire il concetto di non salvato è bruttino e che sarebbe molto meglio scrivere:
this.Id( s => s.Key )
    .ColumnName( "Id" )
    .WithUnsavedValue( new Key<Guid>( Guid.Empty ) )
    .SetAttribute( "type", typeof( GuidKeyUserType ).AssemblyQualifiedName );
Ma in fase di configurazione va tutto in bomba :-) fortunatamente la soluzione ce la fornisce l’Exception stessa: NHibernate cerca di castare il vostro tipo custom da IUserType a IExtendedUserType ed evidentemente fallisce, anche per IExtendedUserType l’implementazione è banale quindi passo.
Generator
Quello che ci manca però è un passaggio fondamentale: dato che stiamo mappando una chiave primaria abbiamo bisogno anche di fornire ad NHibernate un generatore che lo soddisfi, questa è la parte più difficile di tutte :-):
public class GuidKeyGenerator : IIdentifierGenerator
{
    public object Generate( ISessionImplementor session, object obj )
    {
var guid = Guid.NewGuid();
IKey key = new Key<Guid>( guid );
return key; } }
ed ecco il mapping definitivo:
this.Id( s => s.Key )
    .ColumnName( "Id" )
    .SetGeneratorClass( typeof( GuidKeyGenerator ).AssemblyQualifiedName )
    .WithUnsavedValue( Guid.Empty.AsKey() )
    .SetAttribute( "type", typeof( GuidKeyUserType ).AssemblyQualifiedName );
e funziona pure :-) quindi direi che potete tornare a delle pinze di dimensione normale.
.m