Chuck, I don't know if this answers your question about specifying lengths, but we created this custom user type, because for outdated technical reasons we are forced to use CHARs instead of VARCHARs:
Code:
using System;
using System.Data;
using NH = NHibernate;
namespace OurCompany.Framework.DataAccess.Providers.NHibernate.UserTypes
{
/// <summary>
/// An <see cref="NH.UserTypes.IUserType"/> that reads a <c>null</c> value from an <c>string</c>
/// column in the database as a <see cref="String.Empty">String.Empty</see>
/// and writes a <see cref="String.Empty">String.Empty</see> to the database
/// as <c>null</c>. Trailing blanks are trimmed when coming from and going to
/// the database, and are ignored in comparison for equality.
/// </summary>
/// <remarks>
/// This is intended to help with Windows Forms DataBinding and the problems associated
/// with binding a null value. See <a href="http://jira.nhibernate.org/browse/NH-279">
/// NH-279</a> for the origin of this code.
/// </remarks>
public class EmptyTrimmedStringType : NH.UserTypes.IUserType
{
private NH.Type.StringType _stringType;
public EmptyTrimmedStringType()
{
this._stringType = (NH.Type.StringType) NH.NHibernateUtil.String;
}
#region IUserType Members
bool NH.UserTypes.IUserType.Equals(object x, object y)
{
if (object.ReferenceEquals(x, y))
{
return true;
}
return object.Equals(
(x == null || x == DBNull.Value ? String.Empty : x.ToString().TrimEnd()),
(y == null || y == DBNull.Value ? String.Empty : y.ToString().TrimEnd()));
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public NH.SqlTypes.SqlType[] SqlTypes
{
get
{
return new NH::SqlTypes.SqlType[] {this._stringType.SqlType};
}
}
public System.Data.DbType[] DbTypes
{
get
{
return new System.Data.DbType[] {this._stringType.SqlType.DbType};
}
}
public object DeepCopy(object value)
{
return value;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null ||
value == DBNull.Value ||
value.ToString().TrimEnd().Equals(string.Empty))
{
((IDbDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
this._stringType.Set(cmd, value.ToString().TrimEnd(), index);
}
}
public System.Type ReturnedType
{
get
{
return typeof(string);
}
}
public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
{
int index = rs.GetOrdinal(names[0]);
if (rs.IsDBNull(index))
{
return string.Empty;
}
else
{
return rs[index].ToString().TrimEnd();
}
}
public bool IsMutable
{
get
{
return false;
}
}
public int GetHashCode(object obj)
{
return obj.GetHashCode();
}
#endregion
}
}
We then use it in mappings like this:
Code:
<property name="Description" column="description" type="OurCompany.Framework.DataAccess.Providers.NHibernate.UserTypes.EmptyTrimmedStringType, OurCompany.Framework.DataAccess" length="50" />
Note that this user type won't work on ID columns -- NHibernate gets upset if you trim the value. Apparently somewhere it keeps a raw copy of the column value instead of calling NullSafeGet(), and then some direct comparison (rather than calling Equals()) fails. This sounds like a bug to me ...