InvalidCastException from FindDirty on many-to-one property-ref to composite-id

38 views
Skip to first unread message

awatson

unread,
Nov 6, 2008, 9:24:03 AM11/6/08
to nhusers

The objective here is to have a class Widget that contains a single
instance of WidgetCategory. Because we have potentially a whole lot
of Widgets, we want to optimize the storage of WidgetCategories. In
the database, WidgetCategory uses a composite-id made up of Name and
Type -- so any new Widget saved (or old Widget updated) should only
insert a new WidgetCategory if it does not already exist -- and if the
category does exist -- it should reference that. It should never be
updated (and probably never deleted).

We accomplished this by having the WidgetCategory mapped using a
composite-id, but referencing an auto-incrementing identity column in
the database. This mapping works great with new Widget object -- it
will either find the existing WidgetCategory, or insert a new one.
However it fails on the update of an existing Widget with a new (or
existing) WidgetCategory.

<class name="Widget" table="Widget" lazy="false">
<id name="ID">
<generator class="native"/>
</id>

<property name="Name" />
<property name="Data" />

<many-to-one name="MyCategory" column="CategoryId"
class="WidgetCategory" cascade="save-update" property-
ref="PersistanceID">
</many-to-one>
</class>
--------------

<class name="WidgetCategory" table="Category" lazy="false"
mutable="false">
<composite-id>
<key-property name="CategoryName"/>
<key-property name="CategoryType" />
</composite-id>

<property name="PersistanceID" column="PersistanceID" type="int"
generated="always"/>
</class>
----------------

I have an existing Widget, modify its WidgetCategory [either new or
existing], SaveOrUpdate(widget), during the flush [this error only
occurs when I commit] and cascade of the Widget object I receive this
exception:

TestCase 'Widgets.Test.WidgetTests.InsertNewCategoryIntoOldWidget'
failed: System.InvalidCastException: Unable to cast object of type
'System.Int32' to type 'Widgets.Common.WidgetCategory'.
at (Object , GetterCallback )
\NHibernate\Bytecode\Lightweight
\AccessOptimizer.cs(27,0): at
NHibernate.Bytecode.Lightweight.AccessOptimizer.GetPropertyValues(Object
target)
\NHibernate\Tuple\Component
\PocoComponentTuplizer.cs(60,0): at
NHibernate.Tuple.Component.PocoComponentTuplizer.GetPropertyValues(Object
component)
\NHibernate\Type\ComponentType.cs(277,0): at
NHibernate.Type.ComponentType.GetPropertyValues(Object component,
EntityMode entityMode)
\NHibernate\Type\ComponentType.cs(164,0): at
NHibernate.Type.ComponentType.IsDirty(Object x, Object y, Boolean[]
checkable, ISessionImplementor session)
\NHibernate\Type\ManyToOneType.cs(211,0): at
NHibernate.Type.ManyToOneType.IsDirty(Object old, Object current,
Boolean[] checkable, ISessionImplementor session)
\NHibernate\Type\TypeFactory.cs(1023,0): at
NHibernate.Type.TypeFactory.FindDirty(StandardProperty[] properties,
Object[] x, Object[] y, Boolean[][] includeColumns, Boolean
anyUninitializedProperties, ISessionImplementor session)
\NHibernate\Persister\Entity
\AbstractEntityPersister.cs(3437,0): at
NHibernate.Persister.Entity.AbstractEntityPersister.FindDirty(Object[]
currentState, Object[] previousState, Object entity,
ISessionImplementor session)
\NHibernate\Event\Default
\DefaultFlushEntityEventListener.cs(422,0): at
NHibernate.Event.Default.DefaultFlushEntityEventListener.DirtyCheck(FlushEntityEvent
event)
\NHibernate\Event\Default
\DefaultFlushEntityEventListener.cs(176,0): at
NHibernate.Event.Default.DefaultFlushEntityEventListener.IsUpdateNecessary(FlushEntityEvent
event, Boolean mightBeDirty)
\NHibernate\Event\Default
\DefaultFlushEntityEventListener.cs(43,0): at
NHibernate.Event.Default.DefaultFlushEntityEventListener.OnFlushEntity(FlushEntityEvent
event)
\NHibernate\Event\Default
\AbstractFlushingEventListener.cs(163,0): at
NHibernate.Event.Default.AbstractFlushingEventListener.FlushEntities(FlushEvent
event)
\NHibernate\Event\Default
\AbstractFlushingEventListener.cs(61,0): at
NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent
event)
\NHibernate\Event\Default
\DefaultFlushEventListener.cs(18,0): at
NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent
event)
\NHibernate\Impl\SessionImpl.cs(1187,0): at
NHibernate.Impl.SessionImpl.Flush()
\NHibernate\Transaction\AdoTransaction.cs(177,0): at
NHibernate.Transaction.AdoTransaction.Commit()
\Widgets.Test\WidgetTests.cs(193,0): at
Widgets.Test.WidgetTests.InsertNewCategoryIntoOldWidget()

It appears that during the dirty check of Widget's properties NHib
attempts to compare the integer property-ref to the original
WidgetCategory with the new WidgetCategory object itself. Again, this
is only on the update of an existing Widget -- on insert of a new
Widget (even with an existing category) NHib works great -- b/c no
dirty checking obviously. This produces the same behavior in 1.2.1.4
and 2.0.1.

Please, any ideas? I'm sure there is probably a better way to
accomplish this objective, I would like to be able to just save the
widget and have all these changes automatically made -- although I
know this might not be possible. The "widget" I am attempt to save has
a few of these type of relationships. Thanks in advance.

awatson

unread,
Nov 9, 2008, 8:51:17 AM11/9/08
to nhusers
I don't necessarily need help with the error as maybe assistance with
a better mapping for this type of relationship. Or should I just
separate the association and save everything individually?
Reply all
Reply to author
Forward
0 new messages