2009-07-27 1 views
2

У меня есть БД SQL Server с помощью рекурсивной таблицы:Отображение пустых строк в NULL в NHibernate

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

и карта с Fluent NHibernate для

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

Моя проблема заключается в том, что родитель должен быть пустым в мое приложение C#, если столбец «Родитель» - «» (пустая строка) в базе данных и наоборот. К сожалению, я не могу изменить тип столбца, чтобы принять NULL!

Я пытался использовать IEmptyInterceptor, но я не работаю.

Спасибо заранее, forki

ответ

5

Для столбца первичного ключа необходим IUserType, который выполняет специальную обработку значения 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

Я бы пойти на IUserType который будет конвертировать пустую строку в null с и наоборот. Два метода, на которые следует обратить внимание, - NullSafeGet и NullSafeSet.

Не уверен, что как настраиваемые типы интегрируются с Fluent NHibernate.

+0

Я пробовал это. Но, к сожалению, я этого не добился. Должен ли я реализовать IUserType для класса MyTable? – forki23

+0

Ваш класс домена не должен реализовывать интерфейс IUserType, вместо этого вы должны создать отдельный класс пользовательского типа на уровне доступа к данным, который затем будет использоваться при сопоставлении. Таким образом, ваш уровень домена по-прежнему не будет иметь места для данных уровень доступа. Чтобы указать типы пользователей в сопоставлении Fluent NHibernate, используйте метод .CustomTypeIs (). –

+0

IUserType - это, безусловно, путь. Создайте реализацию этого интерфейса отдельно от всего остального, который будет использоваться для преобразования нулевых строк в пустые и наоборот. Затем вы указываете эту реализацию в своих плавных сопоставлениях с использованием CustomTypeIs, как показал Эрик. –

0

Я пытался реализовать IUserType для моего отображения:

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; 
    } 

    ... 
} 

и изменил отображение на

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

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

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

Это похоже на работу для моего корня - но он всегда открывает сессию, чтобы получить право родитель. И это не лениво - поэтому он всегда возвращает всех родителей до корня :-(

Это не может быть правильным способом. Я не хочу открывать новый сеанс, но в противном случае я возвращаю . строка и получить ошибку типа выполнения

0

Рассматривали ли вы с помощью Null Object Pattern вместо

+0

Я думаю, что это та же проблема с шаблоном NUll Object. Я не знаю, как преобразовать пустую строку в мой NullObject. – forki23

+0

Или мне нужно сохранить объект Null в базе данных? – forki23

+0

О да, вы правы, вы попадаете в бесконечный цикл нулевого объекта, содержащегося в нулевом объекте, содержащемся в нулевом объекте. , , aaahhh !!!! –

2

Я нашел (грязный) способ, чтобы получить эту работу:

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]; 
    } 
} 

Спасибо и привет, forki