views:

772

answers:

3

I'm trying to save a mapped entity using NHibernate but my insert to the database fails because the underlying table has a column that does not allow nulls and IS NOT mapped in my domain object. The reason it isn't mapped is because the column in question supports a legacy application and has no relevance to my application - so I'd like to not pollute my entity with the legacy property.

I know I could use a private field inside my class - but this still feels nasty to me. I've read that I can use an NHibernate interceptor and override the OnSave() method to add in the new column right before my entity is saved. This is proving difficult since I can't work out how to add an instance of Nhibernate.type.IType to the types parameter of my interceptor's OnSave.

My Entity roughly looks like this:

public class Client
{
    public virtual int Id { get; set; }
    public virtual int ParentId { get; set; }
    public virtual string Name { get; set; }
    public virtual string Phone { get; set; }
    public virtual string Email { get; set; }
    public virtual string Url { get; set; }
}

And my interceptor

 public class ClientInterceptor : EmptyInterceptor
{

    public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
    {
        if (entity is Client)
        {
            /*
              manually add the COM_HOLD column to the Client entity
            */
            List<string> pn_list = propertyNames.ToList();
            pn_list.Add("COM_HOLD");
            propertyNames = pn_list.ToArray();

            List<Object> _state = state.ToList();
            _state.Add(false);
            state = _state.ToArray();

            //somehow add an IType to types param ??

         }
         return base.OnSave(entity, id, state, propertyNames, types);
    }
}

Does anyone have any ideas on how to do this properly?

A: 

Personally I wouldn't do it so complicated. I would add the private property and assign it a default value - finished. You could also consider a default value in the database, then you don't need to do anything else.

private virtual bool COM_HOLD 
{ 
  get { return false; } 
  set { /* make NH happy */ } 
}

Before writing a interceptor for that I would consider to write a database trigger. Because with the Interceptor you are "polluting" your data access layer. It could make it unstable and you could have strange problems.

Stefan Steinegger
Hi Stefan - thanks for the feedback. Sadly I cannot alter the database, trust me I wish I could. As I set out in my question I'd ideally like not to have to add a property to my entity because COM_HOLD means nothing in the logic of my app - its just a mandatory column in my db's table.
Dav Evans
I understand what you mean. I only suggest to take the easiest solution. A private property is very easy and doesn't hurt much. Messing around with interceptors is not easy at all.
Stefan Steinegger
+1  A: 

I can't say for sure since I've never actually done this (like Stefan, I also prefer to just add a private property), but can you just add a NHibernate.Type.BooleanType to the types array?

List<IType> typeList = types.ToList();
typeList.Add(new BooleanType());
types = typesList.ToArray();

EDIT

Yes, it looks like you are right; the types have an internal constructor. I did some digging and found TypeFactory:

Applications should use static methods and constants on NHibernate.NHibernateUtil if the default IType is good enough. For example, the TypeFactory should only be used when the String needs to have a length of 300 instead of 255. At this point NHibernate.String does not get you thecorrect IType. Instead use TypeFactory.GetString(300) and keep a local variable that holds a reference to the IType.

So it looks like what you want is NHibernateUtil:

Provides access to the full range of NHibernate built-in types. IType instances may be used to bind values to query parameters. Also a factory for new Blobs and Clobs.

typeList.Add(NHibernateUtil.Boolean);
Stuart Childs
I'm afraid not - NHibernate.Type.BooleanType and everything else in that namespace has a private constructor making it impossible to new one up the usual way. I'm guessing you need to use some other NHibernate API to create the objects.
Dav Evans
Try NHibernateUtil.Boolean.
Stuart Childs
Thanks Stuart - that did the trick. I've removed the private field from my entity class and I'm able to insert to the table with no problems.
Dav Evans
Are you sure you got this to work Dav? I don't know how modifying the arrays and passing it into the base EmptyInterceptor could really change anything because when you return from your interceptor implementation the arrays will be set back to what they were in the calling method.I've been trying to get this to work on OnFlushDirty and have not been able to get it to work. Thanks.
Jerry
Why would the arrays be set back to what they were? Arrays are passed by reference. Indeed the method doc explicitly mentions changing them: "Returns true if the user modified the currentState in any way." The only thing I can suggest with the current information is to double check that you're doing what you think you're doing (e.g. OnFlushDirty only fires during a flush *and* the entity in question has been flagged as dirty). If you're still having trouble, I'd suggest opening a new question where more and better discussion can take place.
Stuart Childs
Thanks for responding. Hoping that I am wrong, but I just created a little test app to verify. When you pass an array it is passed by ref, but when you reassign the local variable calling ToArray() you no longer have a ref to the calling methods arr. Am I missing something?class Num{ public Num(int i) { v = i; } public int v { get; }}static void Main(string[] args){ Num[] arr = new Num[] { new Num(0), new Num(1) }; foo(arr);}static void foo(Num[] someArr){ List<Num> list = someArr.ToList(); list.Add(new Num(2)); someArr = list.ToArray();}
Jerry
Sorry about the cruddy code formatting. I guess I shouldn't be doing this in comments. You can find the code here: http://codepaste.net/r4oooxI really want to get this to work, so I don't have to add timestamps to my domain and mapping.I'll open another question about this.
Jerry
A: 

Ive added a post on my blog outlining how this all works for anyone who hits this problem http://www.dav-evans.com/?p=75

Dav Evans