Question

I have Events, which can have many EventTypes. I'm attempting to query the database by using the NHibernate mappings I have defined. The issue is that when defining criteria on child objects, NHibernate makes repeated calls to the database, rather than making one call for all the data it needs. This leads to an extremely slow search.

The database tables are as follows:

Event
-EventID

EventType
-EventTypeID

EventEventTypeID
-EventEventTypeID
-EventID
-EventTypeID

The Event class looks like this:

Id as Integer
Types as IList(Of EventType)

The EventType class looks like this:

Id as Integer
FullTitle as String

There is no EventEventType class.

The NHibernate mappings are as follows:

EventMapping

Table("event.Event")
Id(Function(x) x.Id).Column("EventID").GeneratedBy().Identity()
HasManyToMany(Of EventType)(Function(x) .Types).Table("event.EventEventType").ParentKeyColumn("EventID").ChildKeyColumn("EventTypeID").Cascade.All()

EventTypeMapping

Table("event.EventType")
Id(Function(x) x.Id).Column("EventTypeID").GeneratedBy().Identity()
Map(Function(x) x.FullTitle).Column("EventTypeFullTitle")

On the open of my form, the following function is called, which sets the FetchMode for the Types property of Event.

Public Function CreateListViewQuery(currentNHibernateSession As ISession) As NHibernate.ICriteria Implements IListViewQueryable.CreateListViewQuery

        Return currentNHibernateSession.CreateCriteria(Of [Event])().SetFetchMode("Types", FetchMode.Join)

End Function

This is used to populate the listview, which happens extremely quickly, with just one call to the database for all the data:

SELECT TOP (50)
    this_.EventID as EventID
    , t3_.EventTypeID as EventTyp1_15_0_
    , t3_.EventTypeFullTitle as EventTyp2_15_0_
FROM event.Event this_
    inner join event.EventEventType types5_ on this_.EventID=types5_.EventID 
    inner join event.EventType t3_ on types5_.EventTypeID=t3_.EventTypeID

However, when I add a Criterion.Expression, like this (where QuickSearch is my user input):

.CreateAlias("Types", "t", NHibernate.SqlCommand.JoinType.InnerJoin)
.Add(NHibernate.Criterion.Expression.Like("t.FullTitle", QuickSearch))

I get 1 call which looks like this:

SELECT TOP 50
    this_.EventID as EventID12_6_
    , types8_.EventID as EventID
    , t3_.EventTypeID as EventTyp2_
    , t3_.EventTypeFullTitle as EventTyp2_15_0_ 
FROM
    event.Event this_
        inner join event.EventEventType types8_ on this_.EventID=types8_.EventID
        inner join event.EventType t3_ on types8_.EventTypeID=t3_.EventTypeID
WHERE t3_.EventTypeFullTitle like @p1

And 50 more calls which look like this (50 as I have selected TOP 50):

SELECT
    types0_.*
FROM
    event.EventEventType types0_
        left outer join event.EventType eventtype1_ on types0_.EventTypeID=eventtype1_.EventTypeID
WHERE types0_.EventID=@p0

(where @p0 is each of the TOP 50 Events which are returned by the search)

I feel like only the first call should be necessary for this.

Is it the nature of a many-to-many relationship which means NHibernate needs these additional calls? Is there something in my mapping which I've missed?

Perhaps importantly, I have used exactly the same technique for String properties of Event, and one-to-many relationships from Event and only one call is required for the search. The problem seems to only exist with many-to-many relationships.

Apologies for the long question, and thank you for getting this far. I've read many questions on a similar topic but couldn't find any addressing the issues of repeated database calls for many-to-many relationships.

Was it helpful?

Solution

What you are describing is an nHibernate n+1 issue (basically the number of queries executed is in direct proportion to the result set size), which can be difficult to resolve depending on how complex your query is.

Although not an obvious solution what has worked for me in the past is to change the join type to left outer join as below:

.CreateAlias("Types", "t", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top