Miałem ostatnio issue związane z dużym systemem, gdzie dany były masowo importowane i często składały się z pustych stringów. Równie często zawierały w sobie sporo spacji :) Cóż... taki content w bazie nie pomaga przy wyszukiwaniu z użyciem słowa "LIKE". Rozwiązania były dwa. Dla każdego importowanego rekordu przed zapisem jechać po każdym polu i przetwarzać wartość co zwiększy liczbę linii kodu o jakieś 500% albo pogłówkować, pomęczyć się i napisać własny typ danych dla NHibernate...


Tworzę więc klasę oparta o interfejs IUserType, i w najważniejszych metodach NullSafeGet oraz NullSafeSet sprawdzam czy wartość po operacji Trim zawiera jakiś tekst i ewentualnie ustawiam / zwracam ją jako NULL. Nowa klasa wygląda tak:

using System;
using System.Data;
using NHibernate.UserTypes;
using NHibernate.SqlTypes;
using NHibernate;

namespace blabliblu.NHibernate.Type {
 public class TrimString: IUserType {
  private readonly System.Type RETURNED_TYPE = typeof(String);
  private readonly SqlType[] SQL_TYPES = new SqlType[] {
   NHibernateUtil.String.SqlType
  };

  object IUserType.Assemble(object cached, object owner) {
   return cached;
  }

  object IUserType.DeepCopy(object value) {
   return value;
  }

  object IUserType.Disassemble(object value) {
   return value;
  }

  bool IUserType.Equals(object x, object y) {
   if (ReferenceEquals(x, y)) return true;
   if (x == null || y == null) return false;
   return x.Equals(y);
  }

  int IUserType.GetHashCode(object x) {
   return x == null ? typeof(bool).GetHashCode() + 473 : x.GetHashCode();
  }

  bool IUserType.IsMutable {
   get {
    return false;
   }
  }

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

   if (obj == null) return null;

   String value = (String) obj;
   value = value.Trim();
   if (String.IsNullOrEmpty(value)) return null;
   return value;
  }

  void IUserType.NullSafeSet(IDbCommand cmd, object value, int index) {
   if (value == null) {
    ((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
   } else {
    String stringValue = (String) value;
    stringValue = stringValue.Trim();
    if (String.IsNullOrEmpty(stringValue)) {
     ((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
    } else {
     ((IDataParameter) cmd.Parameters[index]).Value = stringValue;
    }
   }
  }

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

  System.Type IUserType.ReturnedType {
   get {
    return RETURNED_TYPE;
   }
  }

  SqlType[] IUserType.SqlTypes {
   get {
    return SQL_TYPES;
   }
  }
 }
}


Pozostaje zmienić mapowanie w NHibernate odpowiednio dla wszystkich property które chcemy automatycznie trimować i puste stringi zapisywać jako null. Deklaracja brzmi np.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="blabliblu" namespace="blabliblu">
  <class name="blabliblu.some.Cat" table="CAT">
    <property name="companyName" type="blabliblu.NHibernate.Type.TrimString, blabliblu"/>
  </class>
</hibernate-mapping>


Trzeba pamiętać o niebezpieczeństwie jakie niesie ze sobą takie rozwiązanie. Np... zapisywanie haseł dostępowych.. lekarstwem jest... jest to tak oczywiste że nawet nie napiszę.