Question

Consider two entities Person which has a one-to-many collection Vehicles

public class Person
{
    public IList<Vehicle> Vehicles { get; set;}
}

public class Vehicle 
{
   public string Name { get; set;}

   public Person Owner { get; set; }
}

I display a grid of Persons having vehicle and show the name of the first vehicle in the grid. The grid is paginated. I use the following criteria to fetch the data

I have a criteria for loading data for a grid view as

var criteria = DetachedCriteria.For<Person>()
    .CreateAlias("Vehicles","vehicle", JoinType.InnerJoin)
    .SetResultTransformer(new DistinctRootEntityResultTransformer())
    .SetMaxResults(pageSize)
    .SetFirstResult((page - 1) * pageSize)

criteria.Add(Restrictions.Eq("vehicle.Name", "super"));

where page and pageSize are calculated bits.

The problem is since max results and first results are calculated in the database and distinct root is done outside, the number of rows do not match.

Is there a way to resolve this issue ?

Was it helpful?

Solution

This kind of queries should always use subquery instead of any type of JOIN. That also means, that the colleciton item has reference to parent (as in our case).

So, here we create the inner select for Vehicle:

var vehicles = DetachedCriteria.For<Vehicle>();
// add any amount or kind of WHERE parts
vehicles.Add(Restrictions.Eq("vehicle.Name", "super"))
// and essential SELECT Person ID
vehicles.SetProjection( Projections.Property("Owner.ID"));

Now, we can adjust the above query, to work only on a root/parent level:

var criteria = DetachedCriteria.For<Person>()

// instead of this
// .CreateAlias("Vehicles","vehicle", JoinType.InnerJoin)

// we will use subquery
.Add(Subqueries.PropertyIn("ID", vehicles));

// Wrong to use this approach at all
//.SetResultTransformer(new DistinctRootEntityResultTransformer())

.SetMaxResults(pageSize)
.SetFirstResult((page - 1) * pageSize)

That will create SELECT like this:

SELECT p....
FROM Person AS p
WHERE p.ID IN (
  SELECT v.OwnerId
  FROM Vehcile AS v
    WHERE v.Name = 'super' ...
)

See also:

And how to fetch the collection of Vehicles (until now just used for filtering)? The best (if not only) way is to use 1+1 SELECT statements. The easy and built-in solution is batch-size setting. Just mark the collection of Vehicles with this setting (e.g. batch-size="25") and with few more SELECT statements all data will be effectively loaded. See:

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top