2009-07-27 6 views
2

J'ai un DB SQL Server avec une table récursive:cartographie des chaînes vides à NULL dans NHibernate

MyTable: 
ID : string PrimaryKey 
Parent: string references MyTable - NOTNULL !! 

et carte avec Fluent NHibernate

class MyTable 
{ 
    public virtual string ID {get; set;} 
    public virtual MyTable Parent {get; set;} 
} 

Mon problème est que parent devrait être nulle en mon application C# si la colonne Parent est "" (chaîne vide) dans la base de données et vice versa. Malheureusement, je ne peux pas changer le type de colonne pour accepter NULL!

J'ai essayé d'utiliser IEmptyInterceptor mais je n'arrive pas à le faire fonctionner.

Merci à l'avance, forki

Répondre

5

Vous devez avoir un IUserType pour la colonne de clé primaire, qui effectue la gestion spéciale des valeurs NULL.

public MyTableMap() 
{ 
    Id(x => x.EntryNo) 
     // Since the PK is a string, it must be assigned by the application. 
     .GeneratedBy.Assigned() 
     .SetAttribute("type", typeof(SpecialNullValueStringType).AssemblyQualifiedName); 

    References(x => x.Parent); 
} 

public class SpecialNullValueStringType : IUserType 
{ 
    #region IUserType Members 
    public bool IsMutable 
    { 
     get { return false; } 
    } 

    public Type ReturnedType 
    { 
     get { return typeof(string); } 
    } 

    public SqlType[] SqlTypes 
    { 
     get { return new[] { NHibernateUtil.String.SqlType }; } 
    } 

    public object NullSafeGet(IDataReader rs, string[] names, object owner) 
    { 
     var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]); 

     if (obj == null) 
     { 
      return null; 
     } 

     var value = (string) obj; 
     if (String.IsNullOrEmpty(value)) 
     { 
      return null; 
     } 

     return value; 
    } 

    public void NullSafeSet(IDbCommand cmd, object value, int index) 
    { 
     if (value == null) 
     { 
      ((IDataParameter) cmd.Parameters[index]).Value = String.Empty; 
     } 
     else 
     { 
      ((IDataParameter) cmd.Parameters[index]).Value = value; 
     } 
    } 

    public object DeepCopy(object value) 
    { 
     return value; 
    } 

    public object Replace(object original, object target, object owner) 
    { 
     return original; 
    } 

    public object Assemble(object cached, object owner) 
    { 
     return cached; 
    } 

    public object Disassemble(object value) 
    { 
     return value; 
    } 

    public new bool Equals(object x, object y) 
    { 
     if (ReferenceEquals(x, y)) 
     { 
      return true; 
     } 

     if (x == null || y == null) 
     { 
      return false; 
     } 

     return x.Equals(y); 
    } 

    public int GetHashCode(object x) 
    { 
     return x == null ? typeof(string).GetHashCode() + 473 : x.GetHashCode(); 
    } 
    #endregion 
} 
1

Je vais pour une IUserType qui convertirait chaîne vide à null s et vice versa. Deux méthodes à prêter attention sont NullSafeGet et NullSafeSet.

Vous ne savez pas exactement comment les types personnalisés s'intègrent à Fluent NHibernate.

+0

J'ai essayé ceci. Mais malheureusement, je n'ai pas eu ce travail. Dois-je implémenter IUserType pour la classe MyTable? – forki23

+0

Votre classe de domaine ne devrait pas implémenter l'interface IUserType, mais plutôt créer une classe de type d'utilisateur distincte dans la couche d'accès aux données, qui sera ensuite utilisée dans le mappage. De cette façon, votre couche de domaine n'aura pas besoin des données couche d'accès. Pour spécifier les types d'utilisateur dans le mappage NHibernate Fluent, utilisez la méthode .CustomTypeIs (). –

+0

IUserType est définitivement la solution. Créer une implémentation de cette interface séparée de tout le reste, qui sera utilisée pour transformer des chaînes vides en chaînes vides et vice-versa. Vous spécifiez ensuite cette implémentation dans vos mappings courants en utilisant CustomTypeIs comme Erik l'a montré. –

0

J'ai essayé de mettre en œuvre IUserType pour mon application:

public class MyCustomString : IUserType 
{ 
    public Type ReturnedType 
    { 
     get { return typeof (MyTable); } 
    } 

    public SqlType[] SqlTypes 
    { 
     get { return new[] {NHibernateUtil.String.SqlType}; } 
    }  

    public object NullSafeGet(IDataReader rs, string[] names, object owner) 
    { 
     object obj = NHibernateUtil.String.NullSafeGet(rs, names[0]); 

     if (obj == null) return null; 

     var s = (string) obj; 

     if (s == "") 
      return null; 
     using (ISession session = SessionHelper.OpenSession()) 
     { 
      using (session.BeginTransaction()) 
      { 
       return MyTable.Get(session, s); 
      } 
     } 
    } 

    public void NullSafeSet(IDbCommand cmd, object value, int index) 
    { 
     ((IDataParameter) cmd.Parameters[index]).Value = value == null ? 0 : ((MyTable) value).EntryNo; 
    } 

    ... 
} 

et changé le mappage

public MyTableMap() 
    { 
     Id(x => x.EntryNo); 

     Map(x => x.Parent).CustomTypeIs<MyCustomString>(); 

     // References() doesn't allow CustomTypeIs() 
     // References(x => x.Parent).CustomTypeIs<MyCustomString>(); 
    } 

Cela semble fonctionner pour ma racine - mais il ouvre toujours une session pour obtenir le droit parent. Et ce n'est pas paresseux - donc il récupère toujours tous les parents jusqu'à la racine :-(

Cela ne peut pas être le bon chemin.Je ne veux pas ouvrir une nouvelle session - mais sinon je retourne un . chaîne et obtenir une erreur de type d'exécution

0

Avez-vous pensé à utiliser le Null Object Pattern à la place

+0

Je pense que c'est le même problème avec le modèle d'objet NUll. Je ne sais pas comment transformer la chaîne vide en NullObject. – forki23

+0

Ou devrais-je persister l'objet Null dans la base de données? – forki23

+0

Oh oui, vous avez raison, vous entrez dans une boucle infinie de l'objet nul contenu dans l'objet nul contenu dans l'objet nul. . . aaahhh !!!! –

2

J'ai trouvé un (désordre) façon d'obtenir ce travail:

public class NullEventListener : IPreUpdateEventListener, IPreInsertEventListener, IPreLoadEventListener 
{ 
    #region IPreInsertEventListener Members 

    public bool OnPreInsert(PreInsertEvent preInsertEvent) 
    { 
     var instance = preInsertEvent.Entity as MyTable; 
     if (instance == null) 
      return false; 

     if (instance.Parent == null) 
      Set(preInsertEvent.Persister, preInsertEvent.State, "Parent", string.Empty);  

     return false; 
    } 

    #endregion 

    #region IPreLoadEventListener Members 

    public void OnPreLoad(PreLoadEvent preLoadEvent) 
    { 
     var instance = preLoadEvent.Entity as MyTable; 
     if (instance == null) 
      return; 

     try 
     { 
      // this is really messy!! 
      var parent = Get(preLoadEvent.Persister, preLoadEvent.State, "Parent") as MyTable; 
      if (parent == null || parent.ID == "") 
       throw new Exception("Set to null"); 
     } 
     catch (Exception) 
     { 
      Set(preLoadEvent.Persister, preLoadEvent.State, "Parent", null); 
     } 

     return; 
    } 

    #endregion 

    #region IPreUpdateEventListener Members 

    public bool OnPreUpdate(PreUpdateEvent preUpdateEvent) 
    { 
     var instance = preUpdateEvent.Entity as MyTable; 
     if (instance == null) 
      return false; 

     if (instance.Parent == null) 
      Set(preUpdateEvent.Persister, preUpdateEvent.State, "Parent", string.Empty);  

     return false; 
    } 

    #endregion 

    private static void Set(IEntityPersister persister, object[] state, string propertyName, object value) 
    { 
     int index = Array.IndexOf(persister.PropertyNames, propertyName); 
     if (index == -1) 
      return; 
     state[index] = value; 
    } 

    private static object Get(IEntityPersister persister, object[] state, string propertyName) 
    { 
     int index = Array.IndexOf(persister.PropertyNames, propertyName); 
     if (index == -1) 
      return null; 
     return state[index]; 
    } 
} 

Merci et salutations, forki

Questions connexes