NHibernate: custom user type(s)
- Sto studiando quindi prendete tutto molto con le pinze;
- Sono decisamente allergico al manuale delle istruzioni quindi usate delle pinze belle grosse :-);
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
dove IKey è definita così:public interface IUniqueEntity { IKey Key { 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:public interface IKey : IEquatable<IKey>, IComparable { Boolean IsEmpty { get; } }
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:var guid = Guid.NewGuid(); IKey key = new Key<Guid>( guid );
ok, ma che ci metttete al posto dei fatidici “Question Marks”?public class SubjectMapping : ClassMap<Subject> { public SubjectMapping() { this.WithTable( "Subjects" ); this.Id( s => s.Key ) .ColumnName( "Id" ) .WithUnsavedValue( ??? ) .GeneratedBy. ???
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:
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( null ) .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.this.Id( s => s.Key ) .ColumnName( "Id" ) .WithUnsavedValue( new Key<Guid>( Guid.Empty ) ) .SetAttribute( "type", typeof( GuidKeyUserType ).AssemblyQualifiedName );
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 :-):
ed ecco il mapping definitivo:public class GuidKeyGenerator : IIdentifierGenerator { public object Generate( ISessionImplementor session, object obj ) {
var guid = Guid.NewGuid();
IKey key = new Key<Guid>( guid );
return key; } }
e funziona pure :-) quindi direi che potete tornare a delle pinze di dimensione normale.this.Id( s => s.Key ) .ColumnName( "Id" ) .SetGeneratorClass( typeof( GuidKeyGenerator ).AssemblyQualifiedName ) .WithUnsavedValue( Guid.Empty.AsKey() ) .SetAttribute( "type", typeof( GuidKeyUserType ).AssemblyQualifiedName );
.m