Problem
When executing a get for an entity with a many-to-one relationship with outer join set to true, not-found set to ignore and the entity row on the one side does not exist, an extra select is executed by NHibernate trying to load it even though the previous select which was just executed could not find it.
Question
Why is this extra select executed and is there any way to suppress it?
Context
I am working with a legacy database with no foreign key constraints where I have zero control over the schema. Modifying it is unfortunately out of the question. Keep in mind my actual mappings are more complex than this. This occurs several times for the actual entity and they are often loaded by ICriteria.List<T>() and not Session.Load<T>(id), resulting in alot of unnecessary queries and a big performance hit.
Simplified Example
Code
ISession Session = ...
...
Session.Get<Bid>(1);
...
Executed SQL
SELECT bid.Id, bid.ItemId, item.Id FROM Bid bid left outer join Item item on bid.ItemId=item.Id WHERE bid.Id=#p0;#p0 = 1
SELECT item.Id FROM Item item WHERE item.Id=#p0;#p0 = 222
Mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Example" namespace="Example">
<class name="Bid">
<id name="Id">
<generator class="native"/>
</id>
<many-to-one name="Item" column="ItemId" outer-join="true" not-found="ignore" />
</class>
<class name="Item">
<id name="Id">
<generator class="native"/>
</id>
</class>
</hibernate-mapping>
Bid Table Contents
Id ItemId
1 222
Item Table Contents
Id
333
444
OK, looks like it's definitely a bug in NHibernate, as you can see here. At the moment, it's marked as minor, so I guess voting it up might raise it's profile and get a fix going.
Without setting this up, I would guess that the key is to replace the outer-join="true" with fetch="join" in the many-to-one association. By default the fetch mode of a many-to-one association is "select", and I suspect that this is causing the select to be executed.
If you modify that mapping to be:
<many-to-one name="Item" column="ItemId" fetch="join" not-found="ignore" />
it should behave more like you expect it to.
Related
I'm looking at upgrading a legacy application from NHibernate 4 to NHibernate 5 and one of the changes is that now columns cannot be mapped multiple times. However I have an existing mapping which does exactly that and I'm not sure how to resolve it; I need to map the same column for two separate many-to-one relationships from the same entity.
Here is the existing mapping (which obviously works fine with NH 4):
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="..." namespace="...">
<class name="..." table="...">
<id name="Id">
<generator class="identity" />
</id>
<!-- various other properties -->
<many-to-one name="Board" column="Board_Code" />
<many-to-one name="Option">
<column name="Option_Code" />
<column name="Board_Code" />
</many-to-one>
</class>
</hibernate-mapping>
The problem here being the reuse of the Board_Code column.
The column 'Board_Code' has already been added in this SQL builder
It seems to be possible to work around this as detailed in this answer and in this question when both a property and a many-to-one relationship need to be defined. The approach seems to be to just add insert="false" update="false" to the property. However given I'm dealing with two relationships rather than a relationship and a property, I'm not clear on if there's any similar solution (and if so, what the implications would be).
Appreciate any help/advice.
this might work
<many-to-one name="Board" column="Board_Code" update="false" insert="false"/>
I have two tables: "Libros" and "Autores". The relationship is many-to-many.
When I delete an entry from Libros (HibernateTemplate.delete(libro)) this operation delete the record on the intermediate table "Libros_autores" (which is right). But its delete a record on Autores table which is not right for me.
In the mapping file changed the cascade from "all" to "save-update" but it fails because there is an id on table libros_autores which is FK from Libros. So, if Libros record is deleted the row in libros_autores have to be deleted as well to avoid contraints problems (which is absolutly logic for me).
Then my problem is: If I delete a record on table Libros its delete on table autores as well and i only want to be deleted on tables: Libros and libros_autores.
Mapping files are:
Libro.hbm.xml
<class name="app.modelo.Libro" table="libros">
<id name="isbn" column="ISBN" type="string" length="20">
<generator class="assigned" />
</id>
<set name="autores" table="autores_libros" inverse="true" cascade="all,delete-orphan">
<key column="ISBN"/>
<many-to-many column="ID_AUTOR" class="app.modelo.Autor"/>
</set>
</class>
Autor.hbm.xml
<class name="app.modelo.Autor" table="autores">
<id name="id" column="ID_AUTOR" type="integer" length="20">
<generator class="native" />
</id>
<set name="libros" table="autores_libros" cascade="all">
<key column="ID_AUTOR"/>
<many-to-many column="ISBN" class="app.modelo.Libro"/>
</set>
</class>
I understand you should not user cascade="all". I think it means it will really exclude the autor as you exclude the libro. I understand the relationship will be implicity deleted anyway.
See this from the book "Hibernate in Action":
We’ve chosen cascade="save-update" for both ends of the collection; this isn’t unreasonable. On the other hand, cascade="all", cascade="delete", and cas-cade="all-delete-orphans" aren’t meaningful for many-to-many associations, since an instance with potentially many parents shouldn’t be deleted when just one parent is deleted.
I am currently using NHibernate to read a block of 50k rows. It takes almost 20 minutes.
Data which is to be read is prepared by joining more than 5 tables in SQLSERVER 2005.
My main concern is NHibernate prepares a new query for each row retrieval, which is quite time consuming.
I know it's necessary as NHibernate has to map each row to an object, but this approach is useless where reading is main concern.
I cannot use Stateless sesstion as I am using collections to store data
I also need to apply some filter through parameters. I have also implemented Stored Procedure, but somehow it didn't help.
Is there any other way to handle large collection of data.
------Edit-------
Here are my two config file which I was using to map
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="myNameSpace" assembly="myNameSpace">
<class name="MyClass" table="TableNameFromDb">
<id name="ID" column="ID">
<generator class="native" />
</id>
<property name="prop1"/>
<property name="prop2"/>
<bag name="bag1" cascade="all-delete-orphan" inverse="false" fetch="join" lazy="false">
<key column="prop1"/>
<one-to-many class="MyClass2" />
</bag>
</class>
</hibernate-mapping>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="myNameSpace" assembly="myNameSpace">
<class name="MyClass2" table="TableNameFromDb2">
<id name="prop2" column="ID">
<generator class="native" />
</id>
<property name="prop2"/>
<property name="prop3"/>
</class>
</hibernate-mapping>
Now the main concern here is NHibernate is creating a new query for each row and sending to the database which is an overhead.
First of all, I think you'll have to verify what's causing the performance issue.
As Felice Pollano already mentionned, it could be that you're facing a select n+1 issue. This can possibly be solved by modifying your mapping, or, by specifying the FetchMode that must be used in the Criteria that you're using to retrieve the data.
Another thing that you should contemplate: is it really necessary to retrieve all those rows in one go? What are you trying to do? Do you really need all the data that's represented by your entities, or can you use a projection which only loads those columns that you're interested in?
Can you use Paging?
Perhaps you can use a MultiCriteria...
However, in order to solve this issue, I think more input from you is needed.
The problem could be due to the reason; by default, NHibernate does not fully qualify table names within its queries sent to SQL Server. To leverage sp_execsql to its fullest, requests must include fully qualified table names.
To fully qualify NHibernate queries, in app/web.config file, add the following key in NHibernate settings:
<add value="my_DB_name.dbo" key="hibernate.default_schema" />
(replace dbo as necessary.)
Try it and tell us if it gives you any performance boost?
For filtering purpose, I'd like to propose in nhibernate both the mapped property and the id.
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="BusinessObjets.ItemShopping,BusinessObjets" table="ADN_Monture" lazy="true">
<many-to-one name="Manufacturer" column="IDManufacturer" cascade="save-update" not-null="true" />
<property name="IDManufacturer" column="IDManufacturer" type="int" />
</class>
</hibernate-mapping>
In this case, it would propose either a Manufacturer property of the Manufacturer Type and a IDManufacturer (int).
The int would be a readonly property and would only be used to filter the data.
For example :
var result = from item in session.Query<ItemShopping>() select item).ToList<ItemShopping();
and then filter the result with linq to objects with an id.
Is there any drawback in the nhibernate process (insert / update) ?
Regards
Edit
Well after installing nhibernate profiler, I notice that a filter based only on the foreign key (item.Manufacturer.IdManufacturer) doesn't use the proxy. So there is no performance problem.
Can someone confirm ?
You can experience problems in saving/updating the entity. In this case just put update="false" on the additional Id property, and it should work like a charm. No problem if you are just querying. BTW you should not have performance issues even if you query for the many to one. NH known to issue a query based on the Id and does not eagerly fetch anything just for issuing the query.
I have an existing database with the table Transactions in it. I have added a new table called TransactionSequence where each transaction will ultimately have only one record. We are using the sequence table to count transactions for a given account. I have mapped this as a one-to-one mapping where TransactionSequence has a primary key of TransactionId.
The constraint is that there is an instead of trigger on the transaction table does not allow updates of cancelled or posted transactions.
So, when the sequence is calculated and the transaction is saved, NHibernate tries to send an update on the transaction like 'UPDATE Transaction SET TransactionId = ? WHERE TransactionId = ?'. But this fails because of the trigger. How can I configure my mapping so that NHibernate will not try to update the Transaction table when a new TransactionSequence table is inserted?
Transaction mapping:
<class name="Transaction" table="Transaction" dynamic-update="true" select-before-update="true">
<id name="Id" column="ID">
<generator class="native" />
</id>
<property name="TransactionTypeId" access="field.camelcase-underscore" />
<property name="TransactionStatusId" column="DebitDebitStatus" access="field.camelcase-underscore" />
<one-to-one name="Sequence" class="TransactionSequence" fetch="join"
lazy="false" constrained="false">
</one-to-one>
</class>
And the sequence mapping:
<class name="TransactionSequence" table="TransactionSequence" dynamic-update="true">
<id name="TransactionId" column="TransactionID" type="Int32">
<generator class="foreign">
<param name="property">Transaction</param>
</generator>
</id>
<version name="Version" column="Version" unsaved-value="-1" access="field.camelcase-underscore" />
<property name="SequenceNumber" not-null="true" />
<one-to-one name="Transaction"
class="Transaction"
constrained="true"
foreign-key="fk_Transaction_Sequence" />
</class>
Any help would be greatly appreciated...
One to one mapping in nhibernate doesn't work the way you think it does. It's designed so that you have two classes, which when persisted to their corresponding tables have the same primary keys.
However you can make it work, but it's not pretty. I'll show you how then offer up some alternatives:
In your Transaction hbml:
<one-to-one name="Sequence" class="TransactionSequence" property-ref="Transaction"/>
In your Sequence html:
<many-to-one name="Transaction" class="Transaction" column="fk_Transaction_Sequence" />
This should do what you want it to do. Note the property-ref.
The next question you're going to post on is going to ask how you get lazy loading on one-to-one associations. The answer is, you can't... well you can, but it probably won't work. The problem is that you have your foreign key on the sequence table, which means that nhibernate has to hit the database to see if the target exists. Then you can try playing around with constrained="true/false" to see if you can persuade it to lazily load the one-to-one association.
All in all, it's going to result in a total waste of your time.
I suggest either:
Have two many-to-one associations.
Have a many-to-one association with a collection on the other end.
This will save you a lot of headaches in the long run.
Turns out that for my situation a <join table> mapping worked best. I just had to make sure that I made the properties that came from the second table were nullable types, or it would do an insert on save even if nothing had changed. Since I did not need lazy loading for the second table, this works great. I am sure that I could have gotten paired many-to-one mappings to work, but it was not intuitive and seems more complicated than the join table option, however <join table> is only available in NHibernate 2.0 and up.