Search code examples
hibernatenhibernatefluent-nhibernatehqlnhibernate-criteria

NHibernate - fetching with futures


I have this Fluent NHibernate mapping:

public LossMap()
{
    Table("losses");
    Id(x => x.Id).Column("id");
    References(x => x.Policy).Column("pol_id");
    HasMany(x => x.Statuses).KeyColumn("loss_id").Cascade.All().Inverse();
    HasMany(x => x.Reserves).KeyColumn("loss_id").Cascade.All().Inverse();
    HasMany(x => x.Payments).KeyColumn("loss_id").Cascade.All().Inverse();
}

public LossPaymentMap()
{
    Table("losspayments");
    Id(x => x.Id).Column("id");
    Map(x => x.Type).Column("type_id");
    References(x => x.Reserve).Column("reserve_id");
}

public LossReserveMap()
{
    Table("lossreserves");
    Id(x => x.Id).Column("id");
    Map(x => x.Type).Column("type_id");
    Map(x => x.Status).Column("status_id");
    References(x => x.ParentReserve).Column("parent_reserve_id");
}

public LossStatusMap()
{
    Table("lossstatuses");
    Id(x => x.Id).Column("id");
    Map(x => x.Status).Column("status_id");
    Map(x => x.ExpirationDate).Column("expirationdate");
    References(x => x.Loss).Column("loss_id");
}

To summarize:

  1. Loss has many Payments, Reserves and Statuses
  2. Payment has one Reserve

I am trying to fetch Losses and their payments and reserves (but not statuses) with the following constraints:

  1. Only fetch Losses which have at least one status with "status.Status not in (1,2,7)".
  2. Only fetch Loss.Payments where "loss.Payment.Type = 2 and loss.Payment.Reserve.Status != 4)"
  3. Only fetch Loss.Reserves where Reserve.Status != 3

As I am trying to fetch 2 parallel relations, I have to use multiqueries or futures to avoid Cartesian product (right?), as explained here: http://ayende.com/blog/4367/eagerly-loading-entity-associations-efficiently-with-nhibernate

I came up with this query (in HQL):

int[] statuslist = new int[3] {1, 2, 7};

var losses =
session.CreateQuery(
    "from Loss l left join fetch l.Payments as payment join l.Statuses as status where l.Policy.Product.Id = :tid1 " + 
    "and status.Status not in ( :statuslist1) " +
    "and payment.Type = 2 and payment.Reserve.Status != 4")
    .SetParameter("tid1", productid)
    .SetParameterList("statuslist1", statuslist)
    .Future<Loss>();

session.CreateQuery(
    "from Loss l left join fetch l.Reserves as reserve join l.Statuses as status where l.Policy.Product.Id = :tid2 " +
    "and status.Status not in ( :statuslist2) " +
    "and reserve.Status != 3 ")
    .SetParameter("tid2", productid)
    .SetParameterList("statuslist2", statuslist)
    .Future<Loss>();

var list = losses.ToList();

However, when executing this query, I get an error: NHibernate.HibernateException: Failed to execute multi query[..SQL query]---> System.ArgumentException: The value "System.Object[]" is not of type "Entities.Loss" and cannot be used in this generic collection.

Any clues what am I doing wrong here ?

When I remove the status constraint, the query works:

var losses =
session.CreateQuery(
    "from Loss l left join fetch l.Payments as payment where l.Policy.Product.Id = :tid1 " + 
    "and payment.Type = 2 and payment.Reserve.Status != 4")
    .SetParameter("tid1", productid)
    .Future<Loss>();

session.CreateQuery(
    "from Loss l left join fetch l.Reserves as reserve where l.Policy.Product.Id = :tid2 " +
    "and reserve.Status != 3 ")
    .SetParameter("tid2", productid)
    .Future<Loss>();

However, the results are not what I want (I need that constraint).

Any advice ?

Oh, and using HQL is not a "must-be", if this is possible using Linq or QueryOver, I've got not problem with that.

Thanks!


Solution

  • You have a join but have not specified which object you want thus you're actually getting back a tuple.

    You should specify which entities/properties you want in the select, eg:

    select l
    from Loss l left join fetch l.Payments as payment
    where l.Policy.Product.Id = :tid1
    and payment.Type = 2 and payment.Reserve.Status != 4
    

    You should also note that when using a join you can get multiple results for the same entity, if you only want unique entities you should use Transformers.DistinctRootEntity

    For IQuery/ICriteria: .SetResultTransformer(Transformers.DistinctRootEntity)
    For QueryOver: .TransformUsing(Transformers.DistinctRootEntity)