I'm trying to migrate an app from NHibernate 1.x to 2.1, and I've noticed something odd: my immutable objects are being deleted in my integration tests.
For example, this test used to pass:
[Test, ExpectedException(typeof(NHibernate.HibernateException))]
public void DeleteExistingInstanceThrows()
{
// Note: NHib transaction is opened/rolled back in test setup/teardown
m_testRecordId = CreateRecordInDB();
var dao = new NHibFactory().GetTransDao();
var obj = dao.GetById((long)m_testRecordId, false);
dao.Delete(obj);
dao.CommitChanges();
Assert.IsFalse(TestRecordExists(m_testRecordId));
}
The call to CommitChanges should throw, because of trying to delete an instance whose type is mapped with mutable="false". However, after updating the expected exception type, this test now fails (because no exception is thrown). I've checked the database and traced through the test, and the record is created, the instance is loaded, and the record gets deleted again afterwards.
The class in question is mapped to the same table as another class - it is more-or-less intended as a read-only view of a subset of that data. Here's the class mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Sample.Trans, Sample" table="tblTrans" mutable="false">
<id name="Id" column="transId" unsaved-value="0">
<generator class="identity" />
</id>
<property name="Amount" column="amount" type="Decimal" />
<!-- ...a half dozen other mapped fields... -->
</class>
</hibernate-mapping>
I expect I'm missing something basic, but I've been scouring the web for any reason why the mutable attribute might be ignored, and I've come up empty.
UPDATE:
Logging with Log4Net, it's clear that NHibernate was sending the delete command to the database.
After having dug into it, I found this block of code commented out in the DefaultDeleteEventListener's OnDelete method:
/*
if ( !persister.isMutable() ) {
throw new HibernateException(
"attempted to delete an object of immutable class: " +
MessageHelper.infoString(persister)
);
}*/
As a result, no exception is raised when attempting to delete an immutable instance, and the deletion does actually occur afterwards as if the instance were mutable.
I've sent a note to an NHibernate maintainer with my findings - looks like a bug to me.
Noted by another poster, this issue was fixed in release 2.1.1 from Oct 31 2009.
UPDATE:
As of February 2011, NHibernate is now able to delete immutable class instances again. Immutable objects should be deletable - the fact that they were originally not was the actual bug.
I had exactly the same problem in the same situation (upgrading from 1.x). The bug was fixed in 2.1.1 which was released Oct 31.
/Emil
Related
I have this Instrument entity:
<class name="Instrument" table="Instruments" mutable="false">
<id name="ID" column="INSTRUMENT_ID" unsaved-value="0">
<generator class="assigned" />
</id>
<property name="....." />
<property name="....." />
</class>
This entity is used in many-to-one relationship in other entity (InstrumentSelection). This is many-to-one mapping info:
<many-to-one name="Instrument" access="field.camelcase" column="Instrument_ID" update="false" class="Instrument" not-null="true" fetch="join" lazy="false" />
The issue I've it that when I save InstrumentSelection entity with Save:
Transact(() => session.Save(entity));
I get error in logs:
2012-12-20 14:09:54,607 WARN 12 NHibernate.Engine.ForeignKeys - Unable
to determine if Instrument with assigned
identifier 11457 is transient or detached; querying the database. Use
explicit Save() or Update() in session to prevent this.
A few facts about Instrument entity:
It's just a reference entity
It's immutable entity
It can not be added / inserted via application. I get rows in database from external feed.
Question (version 1): A my question is: is there a way to instruct NHibernate to always consider Instrument entity as detached? I mean - if an instance of Instrument exists in application it means that it's present in database. So there is no too much sense in quering the database.
EDIT 1: Because Question (version 1) was not answered yet, let me change it slightly:
Question (version 2): What could be the behaviour that NHibernate is still trying to work out whether entity is detached/transient? I think I have my mapping configured correctly (unsaved-value, generator).
The problem is that when you save the InstrumentSelection, NHibernate is cascading the operation to save the child Instruments. My first suggestion is to set cascade to none on the InstrumentSelect side of the relationship.
My second suggestion is to use an interceptor as shown in this answer.
Recently i had some performance problems in a SOAP webservice I wrote a while ago. I noticed I had a lot of queries going on and my hbm.xml mappings where full of lazy=false statements. I upgraded to NHibernate 3.0 and removed the lazy = false stuff and everything was a LOT faster....but now i am getting the following error:
System.InvalidOperationException: There was an error generating the XML document. ---> System.InvalidOperationException: The type UserProxy was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
User is a class of which i removed the lazy=false property from the class tag like this:
<class name="User" table="Users" >
<id name="DatabaseID" unsaved-value="0" column="ID" type="integer" >
<generator class="native"/>
</id>
<property name="IsExpert"/>
.....more stuff here....
</class>
My webservice has a method like this (simplified a little..in real-life i use a repository-like pattern between the service and nhibernate):
[WebMethod]
public User GetUser(int userid)
{
session = GetCurrentSession();
return session.Load<User>(userid);
}
The webservice expects to serialize a user and NHibernate gives me a UserProxy (which is not a user exactly). How should I overcome this?
Don't return entities from the web method. Use a DTO.
Webservices cannot serialise proxies - session.Load(userId) will return a proxy. You should user session.Get(userId) .
I think the answers saying you should use DTOs are not helpful, there is a time and place for DTOs and sometimes you may just want to return the entity.
If the User has child proxy properties, I have a class for handling this situation. Basically it loops through all properties (using reflection, and recursively going through child objects and collections) and uses the NHibernate.IsInitialized to check whether the property is a proxy or the genuine article. If it is a proxy then it sets it to null, thus making it possible for WCF to serialise it.
Should a lazy loaded collection in NHibernate ever give me a NullReferenceException? I'm getting an exception in a method like the following:
public void Test(ISession session, int id)
{
var entity = session.Load<MyEntity>(id);
entity.LazyLoadedCollection.Add(SomeItem);
}
The call to LazyLoadedCollection is throwing. My mapping looks like:
<bag lazy="true" table="MyTable">
<key>
<column name="LazyLoadedCollection" />
</key>
<many-to-many class="LazyLoadedItem">
<column name="LazyLoadedItemId" />
</many-to-many>
</bag>
Is this the expected behavior in NHibernate?
It's hard to say without seeing your class, but one thing you may not have realized is that you need to populate each collection in your class's constructor.
NHibernate will replace these collections with its own at certain times, but you still need to make sure they're initially populated with a HashedSet<T>, List<T>, or something else depending on the interface you're using.
No it's not. This is a not a good way to ask a question on the internet.
And it's really impossible to give you a direction what to do if you don't post the code throwing the exception and tell us where the exception comes from.
I'm doing some per table inheritance and all is working great- but I'm noticing that when I want the base entity (base table data) NHProf is showing a left outter join on the child entity / (related table)
How can I set the default behavior to only query the needed data - for example: When I want a list of parent elements (and only that data) the query only returns me that element.
right now my mapping is similar to the below:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="FormBase, ClassLibrary1" table="tbl_FormBase">
<id name="BaseID" column="ID" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="ImportDate" column="ImportDate" type="datetime" not-null="false" />
<joined-subclass table="tbl_Form" name="Form, ClassLibrary1">
<key column="ID"/>
<property name="gendate" column="gendate" type="string" not-null="false" />
</joined-subclass>
</class>
</hibernate-mapping>
And the example where I want all the data back vs ONLY the parent entity is shown below:
Dim r As New FormRepository()
Dim forms As List(Of Form) = r.GetFormCollection().ToList()
Dim fbr As New FormBaseRepository()
Dim fb As List(Of FormBase) = fbr.GetFormBaseCollection().ToList()
You can't. It's called "implicit polymorphism" and it's a rather nice (albeit unwanted in your case :-) ) feature provided by Hibernate. When you query a list of base objects, the actual instances returned are of the actual concrete implementations. Hence the left join is needed for Hibernate to find out whether particular entity is a FormBase or a Form.
Update (too big to fit in comment):
The general issue here is that if you were to trick Hibernate into loading only the base entity you may end up with inconsistent session state. Consider the following:
Form instance (that is persisted to both form_base and form tables) was somehow loaded as FormBase.
You've deleted it.
During flush Hibernate (which thinks we're dealing with FormBase and thus is blissfully unaware that there are 2 tables involved) issues a DELETE FROM form statement which throws an exception as FK is violated.
Implicit polymorphism exists to prevent that from happening - Form is always a Form, never a FormBase. You could, of course, use "table-per-hierarchy" mapping where everything is in the same table and thus no joins are needed but you'll end up with (potentially) a lot of NULL columns and - ergo - inability to specify not-null on children's properties.
All that said, if this is REALLY a huge performance issue for you (which it normally shouldn't be - presumably it's an indexed join), you could try using a native query to just return FormBase instances.
I have a many to many relationship between A and B. (I know I can consider refactoring etc, but that's another matter).
my Code does something like this:
// given aId is the Id of an instance of A, and A has a many to many set of B's
A a = myActiveSession.Get<A>(aId);
a.Bs.Add(new B() {Name="dave"});
and I get an exception because a.Bs is NULL.
this only happens in the context of a test suite, and when I run the single test I get a set and everything is ok.
I expect that since the default is lazy fetch, Bs will be initialized when I access the property getter, but if this fails I expect to get an exception, and not simply null... since this way I have no immediate clue what caused this. any ideas?
PS: this is the mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="MyNamespace" assembly="MyAssembly">
<class name="A" table="A" dynamic-update="true">
<id name="id" type="integer" access="field">
<column name="ID"/>
<generator class="native"/>
</id>
<property name="name" type="string" access="field"/>
<set name="Bs" table="A_B">
<key column="a_id"/>
<many-to-many column="b_id" class="B" />
</set>
</class>
</hibernate-mapping>
UPDATE: I've managed to get this to work when I fixed some code that did session cleanup (see #Darin Dimitrov's suggestion), however, I still don't understand what could have caused this strange behavior (instead of receiving some clear exception). so at the moment this remains a mystery.
Unit tests could execute in parallel from different threads and for this reason they should be independent. I suspect that in your case the Session object is reused in multiple tests and one another test could messes up with Bs property. Make sure the session is created inside your test and is destroyed afterwards i.e.
using (var session = sessionFactory.OpenSession())
{
A a = myActiveSession.Get<A>(aId);
}