R – NHibernate criteria query question

nhibernate

I have 3 related objects (Entry, GamePlay, Prize) and I'm trying to find the best way to query them for what I need using NHibernate. When a request comes in, I need to query the Entries table for a matching entry and, if found, get a) the latest game play along with the first game play that has a prize attached. Prize is a child of GamePlay and each Entry object has a GamePlays property (IList).

Currently, I'm working on a method that pulls the matching Entry and eagerly loads all game plays and associated prizes, but it seems wasteful to load all game plays just to find the latest one and any that contain a prize.

Right now, my query looks like this:

var entry = session.CreateCriteria<Entry>()
    .Add(Restrictions.Eq("Phone", phone))
    .AddOrder(Order.Desc("Created"))
    .SetFetchMode("GamePlays", FetchMode.Join)
    .SetMaxResults(1).UniqueResult<Entry>();

Two problems with this:

  1. It loads all game plays up front. With 365 days of data, this could easily balloon to 300k of data per query.
  2. It doesn't eagerly load the Prize child property for each game. Therefore, my code that loops through the GamePlays list looking for a non-null Prize must make a call to load each Prize property I check.

I'm not an nhibernate expert, but I know there has to be a better way to do this. Ideally, I'd like to do the following (pseudocode):

entry = findEntry(phoneNumber)
lastPlay = getLatestGamePlay(Entry)
firstWinningPlay = getFirstWinningGamePlay(Entry)

The end result of course is that I have the entry details, the latest game play, and the first winning game play. The catch is that I want to do this in as few database calls as possible, otherwise I'd just execute 3 separate queries.

The object definitions look like:

public class Entry 
{
    public Guid Id {get;set;}
    public string Phone {get;set;}
    public IList<GamePlay> GamePlays {get;set;}
    // ... other properties
}

public class GamePlay 
{
    public Guid Id {get;set;}
    public Entry Entry {get;set;}
    public Prize Prize {get;set;}
    // ... other properties
}

public class Prize
{
    public Guid Id {get;set;}
    // ... other properties
}

The proper NHibernate mappings are in place, so I just need help figuring out how to set up the criteria query (not looking for HQL, don't use it).

Best Answer

since you are doing this in each request maybe it should be better to set up two formula-properties in your entity. The first one should fetch the latest Gameplay-Id and the other the first Gameplay-Id with a not Null property

this could be as such in the xml mapping file of Entry

<property name="LatestGameplay" formula="select top(1)gp.Id from Gameplay gp where gp.FK_EntryId = PK_EntryId order by gp.InsertDate desc" />

this leaves you with the Gameplay Id's on the Entry entity and after you fetch it it would require another round trip to the DB to GetById-fetch the gameplay's

Alternatively you could work-around using filters. Set the collection back to "lazy" and create these nice filters

Gameplay latest = NHibernateSession.CreateFilter(entry.GamePlays , "order by InsertDate desc").SetMaxResults(1).SetFirstResult(1).UniqueResult<Gameplay>();

Gameplay winner = NHibernateSession.CreateFilter(entry.GamePlays , "where FK_PrizeId is not null order by InsertDate asc ").SetMaxResults(1).SetFirstResult(1).UniqueResult<Gameplay>();

And IFilters can be used in a multiquery as so have 2 db hits: one for the original Entry and one for the multiquery.

Last but not least, you could define 2 bags in the Entry entity, one IList<GamePlay> Latest and one IList<Gameplay> Winner which in the Entry mapping file would be filtered with the appropriate query (although i don't remember now if you can define TOP clauses in the filters) and set those as non-lazy. Then with a single round-trip you can have all the data you want with the following (ugly) syntax

Entry entry = findEntry(phoneNumber);
Gameplay winner = entry.Winner[0]; //check this if null first
Gameplay Latest = entry.Latest[0]; //ditto

note that of all the solutions the 3rd is the one that provides a mechanism to generate additional queries, as the bag can be used in a Criteria/HQL query

Related Topic