I have an NHibernate implementation working with a legacy database (DB2) that I have been asked to modify and I'm running into an issue with getting an id generator to work on an id property that is defined as a customer user type.
The data in a table is mapped to a class similar to the following:
// CLASS FILE
public class Request {
public virtual int Id { get; set; }
... other data properties ...
}
//MAPPING FILE
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="LibraryName" namespace="LibraryName.Domain" default-cascade="none">
<class name="Request" schema="..." table="..." where="...">
<id name="Id" column="..." type="LibraryName.Domain.DB2UserTypes.NullableIntegerType, LibraryName" />
... other data property mappings ...
The legacy database has tables with columns that are foreign keys into the Request's table. The columns containing these foreign keys are NOT nullable so the custom NullableIntegerType is used to write a zero into the foreign key column when a Request object property is null in a referencing object.
// NullableIntegerType Definition
public class NullableIntegerType : IEnhancedUserType
{
private static readonly SqlType[] SQL_TYPES = { NHibernate.NHibernateUtil.Int32.SqlType };
public SqlType[] SqlTypes { get { return SQL_TYPES; } }
public new bool Equals(object x, object y)
{
if (object.ReferenceEquals(x, y))
return true;
else if ((x == null) && (y == null))
return true;
else if ((x == null) || (y == null))
return false;
else
return x.Equals(y);
}
public object DeepCopy(object value) { return value; }
public bool IsMutable { get { return false; } }
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 int GetHashCode(object obj) { return obj.GetHashCode(); }
public Type ReturnedType { get { return typeof(int); } }
public object NullSafeGet(IDataReader dr, string[] names, object owner)
{
object obj = NHibernate.NHibernateUtil.Int32.NullSafeGet(dr, names[0]);
if (obj == null)
return null;
else
{
int result = (int)obj;
if ((result == 0) || (result == 9999999))
return null;
else
{
return int.Parse(result.ToString());
}
}
}
public void NullSafeSet(IDbCommand cmd, object obj, int index)
{
if (obj == null)
NHibernateUtil.Int32.NullSafeSet(cmd, 0, index);
else
NHibernateUtil.Int32.NullSafeSet(cmd, obj, index);
}
public object FromXMLString(string xml)
{
return int.Parse(xml);
}
public string ToXMLString(object obj)
{
return ((int)obj).ToString();
}
public string ObjectToSQLString(object obj)
{
return ((int)obj).ToString();
}
}
I have been asked to modify the mapping so that we can create new Request objects and have the ID be generated using a generator. I created the generator class and modified the Request object mapping to use the generator for the Id.
// GENERATOR CLASS
public class RequestGenerator : TableGenerator
{
public override object Generate(NHibernate.Engine.ISessionImplementor session, object obj)
{
using (IDbCommand command = session.Connection.CreateCommand())
{
command.CommandText = @"...";
command.CommandType = CommandType.StoredProcedure;
var parameter = command.CreateParameter();
parameter.DbType = DbType.Int32;
parameter.ParameterName = "@generatedId";
parameter.Value = 0;
parameter.Direction = ParameterDirection.InputOutput;
command.Parameters.Add(parameter);
command.ExecuteNonQuery();
return Convert.ToInt32(parameter.Value);
}
}
}
//MODIFIED MAPPING FILE
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="LibraryName" namespace="LibraryName.Domain" default-cascade="none">
<class name="Request" schema="..." table="..." where="...">
<id name="Id" column="..." type="LibraryName.Domain.DB2UserTypes.NullableIntegerType, LibraryName" >
<generator class="LibraryName.Domain.DB2Generators.RequestGenerator, LibraryName" />
</id>
... other data property mappings ...
When I attempt to test the modified mapping I get an error before my code even runs that NHibernate is unable to instantiate the ID generator.
// Sample Code
Request request = new Request() { Id = 0, ... other properties }
session.Save(request);
session.Flush();
// Exception Details and Stack Trace
NHibernate.MappingException was unhandled by user code
Message=could not instantiate id generator: LibraryName.Domain.DB2Generators.RequestGenerator, LibraryName
Source=NHibernate
StackTrace:
at NHibernate.Id.IdentifierGeneratorFactory.Create(String strategy, IType type, IDictionary`2 parms, Dialect dialect)
at NHibernate.Mapping.SimpleValue.CreateIdentifierGenerator(Dialect dialect, String defaultCatalog, String defaultSchema, RootClass rootClass)
at NHibernate.Impl.SessionFactoryImpl..ctor(Configuration cfg, IMapping mapping, Settings settings, EventListeners listeners)
at NHibernate.Cfg.Configuration.BuildSessionFactory()
at LibraryName.Infrastructure.NH.NHibernateSessionFactory.createSessionFactory() in C:\...\NHibernate\NHibernateSessionFactory.cs:line 23
at LibraryName.Infrastructure.NH.NHibernateSessionFactory.get_SessionFactory() in C:\...\NHibernate\NHibernateSessionFactory.cs:line 15
at LibraryName.Infrastructure.NH.NHibernateSessionFactoryProvider.CreateInstance(IContext context) in C:\...\NHibernate\NHibernateSessionFactoryProvider.cs:line 13
at Ninject.Activation.Provider`1.Create(IContext context)
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters)
at LibraryName.Infrastructure.InternalMvcModule.<Load>b__0(IContext c) in C:\...\Ninject\InternalMvcModule.cs:line 16
at Ninject.Activation.Providers.CallbackProvider`1.CreateInstance(IContext context)
at Ninject.Activation.Provider`1.Create(IContext context)
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at Ninject.Planning.Targets.Target`1.GetValue(Type service, IContext parent)
at Ninject.Planning.Targets.Target`1.ResolveWithin(IContext parent)
at Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target)
at Ninject.Activation.Providers.StandardProvider.<>c__DisplayClass4.<Create>b__2(ITarget target)
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at Ninject.Activation.Providers.StandardProvider.Create(IContext context)
at Ninject.Activation.Context.Resolve()
at Ninject.KernelBase.<>c__DisplayClass10.<Resolve>b__c(IBinding binding)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at Ninject.Web.Mvc.NinjectDependencyResolver.GetService(Type serviceType)
at System.Web.Mvc.DefaultControllerFactory.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType)
InnerException: System.ArgumentException
Message=type is not a ValueTypeType
Parameter name: type
Source=NHibernate
ParamName=type
StackTrace:
at NHibernate.Id.TableGenerator.Configure(IType type, IDictionary`2 parms, Dialect dialect)
at NHibernate.Id.IdentifierGeneratorFactory.Create(String strategy, IType type, IDictionary`2 parms, Dialect dialect)
InnerException:
If I remove the custom type (type="LibraryName.Domain.DB2UserTypes.NullableIntegerType, LibraryName") from the id mapping then the generator works correctly and my object saves to the database. How can I make the generator and the user defined type on the Id work together?