First let me apologize for the length of this post, this is basically code, although I hope you all carry with me!
I have a script for working with an outdated database where I needed to write IUserType using NHibernate 3.2 to take a 2-digit status field and return a boolean value from it. The status field may contain 3 possible values:
* 'DI' // 'Disabled', return false * ' ' // blank or NULL, return true * NULL
Here is what I have simplified.
Table definition:
CREATE TABLE [dbo].[Client]( [clnID] [int] IDENTITY(1,1) NOT NULL, [clnStatus] [char](2) NULL, [clnComment] [varchar](250) NULL, [clnDescription] [varchar](150) NULL, [Version] [int] NOT NULL )
Free match:
public class ClientMapping : CoreEntityMapping<Client> { public ClientMapping() { SchemaAction.All().Table("Client"); LazyLoad(); Id(x => x.Id, "clnId").GeneratedBy.Identity(); Version(x => x.Version).Column("Version").Generated.Never().UnsavedValue("0").Not.Nullable(); OptimisticLock.Version(); Map(x => x.Comment, "clnComment").Length(250).Nullable(); Map(x => x.Description, "clnDescription").Length(250).Nullable(); Map(x => x.IsActive, "clnStatus").Nullable().CustomType<StatusToBoolType>(); } }
IUserType implementation:
public class StatusToBoolType : IUserType { public bool IsMutable { get { return false; } } public Type ReturnedType { get { return typeof(bool); } } public SqlType[] SqlTypes { get { return new[] { NHibernateUtil.String.SqlType }; } } 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(bool).GetHashCode() + 473 : x.GetHashCode(); } public object NullSafeGet(IDataReader rs, string[] names, object owner) { var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]); if (obj == null) return true; var status = (string)obj; if (status == " ") return true; if (status == "DI") return false; throw new Exception(string.Format("Expected data to be either empty or 'DI' but was '{0}'.", status)); } public void NullSafeSet(IDbCommand cmd, object value, int index) { var parameter = ((IDataParameter) cmd.Parameters[index]); var active = value == null || (bool) value; if (active) parameter.Value = " "; else parameter.Value = "DI"; } }
However, this does not work. This unit test fails with an inaccurate count.
[TestMethod] public void GetAllActiveClientsTest() {
The reason it fails is because it generates the following SQL:
SELECT count(this_.clnID) as y0_ FROM Client this_ WHERE this_.clnstatus = @p0;
But I need this to generate this:
SELECT count(this_.clnID) as y0_ FROM Client this_ WHERE (this_.clnstatus = @p0 <b> OR this_.clnstatus IS NULL);</b>
After some debugging, I saw that the NullSafeSet () method in my StatusToBoolType class was called before the request was created, so I managed to get around this by writing some hacker code in this method to control SQL in cmd. CommandText Property
... public void NullSafeSet(IDbCommand cmd, object value, int index) { var parameter = ((IDataParameter) cmd.Parameters[index]); var active = value == null || (bool) value; if (active) { parameter.Value = " "; if (cmd.CommandText.ToUpper().StartsWith("SELECT") == false) return; var paramindex = cmd.CommandText.IndexOf(parameter.ParameterName); if (paramindex > 0) {
But this is NHibernate !!! Hacking sql statements like this might not be the right way to handle this? Right?
Since this is a common obsolete database, I cannot change the table schema to NOT NULL, otherwise I would just do this and avoid this scenario.
So finally, after all this foreplay, my question is just like this, where can I tell NHibernate to create a custom SQL criteria statement for this IUserType?
Thank you all in advance!