NHibernate, save null reference value in DB as 0 (zero)
-
13-12-2019 - |
Question
I found this example of a tuplizer to do save 0 when saving null relationships. This is needed since I'm working on a app on a legacy database schema.
I tried the tuplizer here: http://nhforge.org/blogs/nhibernate/archive/2011/01/28/how-to-use-0-instead-of-null-for-foreign-keys.aspx
In that example, I got a nullreferenceexception for the ProxyFactory. Then I found an update to the code here: https://bitbucket.org/jfromaniello/hotgazpachoeg/changeset/87ac41c473ae
However, that doesn't work for me either. In the last method, SetPropertyValues (described as dirty hack 3, used when reading an object from DB), I get a nullref exception on this part, if(typeof(IEntity), when reading a not related object (not a Sample)
My mapping is as follows (simplified):
Table("ej_sample");
Not.LazyLoad();
Id(s => s.Id, "sampleID").GeneratedBy.Native();
References<Sample>(s => s.ParentSample, "parentSampleID").NotFound.Ignore();
The parentSampleID column must be 0 when no such object exists.
I figured, I only have to do the dirty hacks on insert and update (possibly in my case only insert).
On insert, I want to create a fake proxy, but the code in [2] loads the entity from the db (possibly to use a Null object?!).
Insert dirty hack:
public override object[] GetPropertyValuesToInsert(object entity, IDictionary mergeMap, ISessionImplementor session) {
var values = base.GetPropertyValuesToInsert(entity, mergeMap, session);
//dirty hack 1
for(int i = 0; i < values.Length; i++) {
if(values[i] == null && typeof(IEntity).IsAssignableFrom(getters[i].ReturnType)) {
values[i] = ((ISession)session).Load(getters[i].ReturnType, 0);
}
}
return values;
}
I tried creating a fake proxy instead of doing the above:
public override object[] GetPropertyValuesToInsert(object entity, IDictionary mergeMap, ISessionImplementor session) {
var values = base.GetPropertyValuesToInsert(entity, mergeMap, session);
//dirty hack 1
for(int i = 0; i < values.Length; i++) {
if(values[i] == null && typeof(IEntity).IsAssignableFrom(getters[i].ReturnType)) {
//values[i] = ((ISession)session).Load(getters[i].ReturnType, 0);
values[i] = CreateFakeProxy(i);
}
}
return values;
}
private object CreateFakeProxy(int i) {
object proxy;
using(var sessionImplementor = _sessionFactory.OpenSession()) {
proxy = _sessionFactory
.GetEntityPersister(getters[i].ReturnType.FullName)
.CreateProxy(0, (ISessionImplementor)sessionImplementor);
}
return proxy;
}
Then I get a nullref exception on the _sessionfactory, which is set in the ctor:
private readonly ISessionFactoryImplementor _sessionFactory;
public NullableTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity)
: base(entityMetamodel, mappedEntity) {
_sessionFactory = entityMetamodel.SessionFactory;
}
Any ideas how to accomplish this?
Solution
The easy solution is to add the following preinsert and preupdate listener.
public class NullToZeroEventListener : AuditEventListener, IPreInsertEventListener, IPreUpdateEventListener
{
public bool OnPreInsert(PreInsertEvent @event) {
ZeroNullIds(@event.State, @event.Persister.PropertyNames);
return false;
}
public bool OnPreUpdate(PreUpdateEvent @event) {
ZeroNullIds(@event.State, @event.Persister.PropertyNames);
return false;
}
protected internal void ZeroNullIds(Object[] state, string[] propertyNames) {
for(int i = 0; i < propertyNames.Length; i++) {
if(state[i] != null || propertyNames[i].EndsWith("ID")) continue;
state[i] = 0;
}
}
}
In the mapping be sure to ignore 0 ids, for example:
References<User>(s => s.User, "userID").NotFound.Ignore().LazyLoad();
In your sessionfactory, add a listener for both preinsert and preupdate events (first shown here):
.ExposeConfiguration(c =>
{
if(!c.EventListeners.PreInsertEventListeners.Any()) {
c.AppendListeners(ListenerType.PreInsert, new IPreInsertEventListener[] { new NullToZeroEventListener() });
}
});