Search code examples
c#nhibernatequeryovernhibernate-projections

How to project the Collections in NHibernate?


Is it possible to project the collections in NHibernate?

For Example:

    User
    {
        UserGuid,
        IList<Groups> UserGroups,
    }
    
    User userEntity = null;

    _session
        .StatefulSession.QueryOver(() => userEntity)
        .SelectList(list => list
             .Select(x => x.UserGuid).WithAlias(() => userEntity.UserGuid)

             //How can I project group collection here?
             .Select(x => x.Groups).WithAlias(() => userEntity.Groups)
        )
        .TransformUsing(Transformers.AliasToBean<UserEntity>())
        .List();

Solution

  • We have to thing about projection as a single SELECT clause. What we would be able to do with it on a DB side, we can mimic with QueryOver. So, in this case, we can join User and Group, and project few properties from User and few from Group. The best target would be some DTO...

    The syntax could be like this:

    ResultDTO resultDTO = null; // the target to be returned
    Group groupEntity = null;
    User userEntity = null;
    
    _session.StatefulSession
        .QueryOver(() => userEntity)
        .JoinAlias(() => userEntity.Groups, () => groupEntity)
        .SelectList(list => list
            .Select(x => x.UserGuid).WithAlias(() => resultDTO.UserGuid)
            // more about User - targeting the ResultDTO
            ...
            // almost the same for Group properties
            .Select(x => groupEntity.Name).WithAlias(() => resultDTO.GroupName)
            ..
        )
        .TransformUsing(Transformers.AliasToBean<ResultDTO >())
        .List();
    

    But there are some issues arround, which in fact effectively disallow this approach. We will reach the Cartesian product, because each row of a User will be multiplied as many times, as many Groups it does have.

    Row 1 - User A - Group A
    Row 2 - User A - Group B // two rows for a user A
    Row 3 - User B - Group B
    Row 4 - User B - Group C
    

    One way to solve it, is to skip the SelectList() (do not use projections) and ask NHiberante to use different transformation:

    _session.StatefulSession
        .QueryOver(() => userEntity)
        .JoinAlias(() => userEntity.Groups, () => groupEntity)
        .TransformUsing(Transformers.DistinctRootEntity)
        .List();
    

    Nice, one would say, but it is wrong again. No effective paging is possible, because the effect of more rows for each User still remains. So, if we would say Take(2), we will get only User A. The reason is hidden in fact, that the narrowing is done expost, in memory, on .NET/Application tier side - not in DB

    Suggestion: Do not use any joins among one-to-many relations. Use the separated queries with conjunction of powerful NHibernate feature: 19.1.5. Using batch fetching

    Please, see also: