Fluent Nhibernate filter and project on collection and association - fluent-nhibernate

class parent
{
virtual AnotherType AnotherType {get;set;}
virtual TypeA TypeA {get;set;}
virtual IList<TypeB> TypeBs {get;set;}
}
class TypeA
{
virtual TypeC TypeC {get;set;}
...
}
class TypeB
{
virtual TypeD TypeD {get;set;}
...
}
Need to return distinct list of parent by filtering on TypeA and TypeB
select p.*
from parent p
join typea a on a.parentid = p.parentid
join typeb b on b.parentid = p.parentid
join typec c on c.typeaid = c.typeaid
join typed d on d.typebid = d.typebid
where p.AnotherType.id = "someid"
and c.foo == "foo"
and d.bar == "bar"
tried using
Session.QueryOver(() => parent)
.joinalias(() => parent.TypeA, () => typea)
.joinalias(() => typea.TypeC, () => typec)
.joinalias(() => parent.TypeB, () => typeb)
.joinalias(() => typeb.TypeD, () => typed)
.where(() => parent.AnotherType.id == "someid")
.and(() => typec.foo == "foo")
.and(() => typed.bar == "bar).Future<parent>.ToArray();
but I'm getting duplicates because of typeB is getting flattened into results rather than queried separately as a collection on parent.

filter the duplicates with
Session.QueryOver(() => parent)
...
.TransformUsing(Transformers.DistinctRootEntity)
.Future<Parent>().ToArray();

Related

NHibernate Projections (QueryOver.SelectList) limits

I'm new on stackoverflow and i hope this question will be appreciated.
Simply said: I select everything from table x left outer join table y. Table x has way too many columns so i make new object x. This object is used for the Projection. I can project every single column of table x i want. But when i try to project/select an collection (the collection of table y) then i get the same error: 'Index was outside the bounds of the array'.
My question: Does NHibernate support to select/project a collection at all? Because i've seen this question multiple times by searching on google (and stackoverflow), but none of those questions were answered.
Code example:
public class X
{
public virtual int ID { get; set; }
public virtual int IDontNeedMoreInfoAboutClassXItTakesToMuchTimeToRetrieve { get; set; }
public virtual IList<Y> YCollection { get; set; }
}
public class Y
{
public virtual int YID { get; set; }
}
public class XRepository{
public ISession Session {get; set;}
public IList<X> Get()
{
X xAlias = null;
Y yAlias = null;
X resultAlias = null;
return Session.QueryOver<X>()
.JoinAlias(() => xAlias.YCollection, () => yAlias, JoinType.LeftOuterJoin)
.SelectList(list => list
.SelectGroup(() => xAlias.ID).WithAlias(() => resultAlias.ID)
.SelectGroup(() => xAlias.YCollection).WithAlias(() => resultAlias.YCollection)) // Index was outside the bounds of the array
.TransformUsing(Transformers.AliasToBean<X>()).List<X>();
}
}
the results returned have to be grouped with the contents intact (so sql is out since it can only aggregate over the grouped items) but NHibernate can only do this for m,apped entities because there it knows the identity to group by. It is simple to do it manually
Y yAlias = null;
var tempResults = Session.QueryOver<X>()
.JoinAlias(x => x.YCollection, () => yAlias, JoinType.LeftOuterJoin)
.SelectList(list => list
.Select(() => xAlias.Id)
.Select(() => xAlias.Name)
...
.Select(() => yAlias.Id)
...
.ToList<object[]>()
List<X> results = new List<X>(100); // switch to Hashset if too slow and order does not matter
foreach(var row in tempResults)
{
Y y = new Y { Id = (long)row[4], ... };
X x = results.FirstOrDefault(x => x.Id == (long)row[0]));
if (x != null)
x.YCollection.Add(y);
else
results.Add(new X { Id = (long)row[0], ..., YCollection = { y } };
}
return results;

nhibernate: select parents and newest child

I have the following entities
public class Vehicle
{
public virtual string InternalNumber { get; set; }
public virtual IList<VehicleDriverHistory> DriverHistory { get; set; }
}
public class VehicleDriverHistory : HistoryEntity
{
public virtual Vehicle Vehicle { get; set; }
public virtual DriverPerson Driver { get; set; }
public virtual DateTime Date { get; set; }
}
Now I need to select every Vehicle and, if present, the newest VehicleDriverHistory-Entry according to "Date".
But I'm kinda struggling, I have no problem doing it in sql, but failed in nhibernate so far.
What I have so far:
VehicleDriverHistory vdHistory = null;
VehicleDriverHistory vdHistory2 = null;
q.Left.JoinAlias(x => x.DriverHistory, () => vdHistory);
q.Left.JoinAlias(x => x.DriverHistory, () => vdHistory2).Where(x => vdHistory2.Date > vdHistory.Date);
q.Where(x => vdHistory2.Id == null);
This doesn't work and it was just my attempt to "translate" the sql query (which yields the correct data) to nhibernate.
So, how would I select parents and the newest child (or none if none is present)
Thanks
UPDATE
With Firos help I ended up with the following:
VehicleDriverHistory historyAlias = null;
var maxDateQuery = QueryOver.Of<VehicleDriverHistory>()
.Where(h => h.Vehicle == historyAlias.Vehicle)
.Select(Projections.Max<VehicleDriverHistory>(h => h.Date));
var vehiclesWithEntry = DataSession.Current.QueryOver(() => historyAlias)
.WithSubquery.WhereProperty(h => h.Date).Eq(maxDateQuery)
.Fetch(h => h.Vehicle).Eager
.Future();
Vehicle VehicleAlias = null;
var vehiclesWithoutEntry = DataSession.Current.QueryOver<Vehicle>(() => VehicleAlias)
.WithSubquery
.WhereNotExists(QueryOver.Of<VehicleDriverHistory>()
.Where(x => x.Vehicle.Id == VehicleAlias.Id).Select(x => x.Id))
.Future();
return vehiclesWithEntry.Select(h => new PairDTO { Vehicle = h.Vehicle, NewestHistoryEntry = h }).Concat(vehiclesWithoutEntry.Select(v => new PairDTO { Vehicle = v })).ToList();
I had to replace the Any() statement in vehclesWithoutEntry with a subquery-clause because it raised an exception.
some other idea using Futures to make only one roundtrip
VehicleDriverHistory historyAlias = null;
var maxDateQuery = QueryOver.Of<VehicleDriverHistory>()
.Where(h => h.Vehicle == historyAlias.Vehicle)
.Select(Projections.Max<VehicleDriverHistory>(h => h.Date));
var vehiclesWithEntry = session.QueryOver(() => historyAlias)
.WithSubquery.WhereProperty(h => h.Date).Eq(maxDateQuery)
.Fetch(h => h.Vehicle).Eager
.Select(h => new PairDTO { Vehicle = h.Vehicle, NewestHistoryEntry = h })
.Future<PairDTO>();
var vehiclesWithoutEntry = session.QueryOver<Vehicle>()
.Where(v => !v.DriverHistory.Any())
.Select(v => new PairDTO{ Vehicle = v })
.Future<PairDTO>();
return vehiclesWithoutEntry.Concat(vehiclesWithEntry); // .ToList() if immediate executing is required
Update: i can not reproduce the exception but you could try this
VehicleDriverHistory historyAlias = null;
var maxDateQuery = QueryOver.Of<VehicleDriverHistory>()
.Where(h => h.Vehicle == historyAlias.Vehicle)
.Select(Projections.Max<VehicleDriverHistory>(h => h.Date));
var vehiclesWithEntry = session.QueryOver(() => historyAlias)
.WithSubquery.WhereProperty(h => h.Date).Eq(maxDateQuery)
.Fetch(h => h.Vehicle).Eager
.Future();
var vehiclesWithoutEntry = session.QueryOver<Vehicle>()
.Where(v => !v.DriverHistory.Any())
.Select(v => new PairDTO{ Vehicle = v })
.Future<PairDTO>();
return vehiclesWithoutEntry.Select(h => new PairDTO { Vehicle = h.Vehicle, NewestHistoryEntry = h }).Concat(vehiclesWithEntry);

How use a select in simple QueryOver

I have a query by QueryOver In Nhibernate 3.1
var q = SessionInstance.QueryOver<Person>()
.Where(p => p.Code == code);
.Select(p => p.Name,p => p.Code);
return q.SingleOrDefault();
This query without select is correct, but with select has a runtime error by this message: Unable to cast object of type 'System.String' to type 'MyNameSpace.Domain.Entities.Person'. How can i select some field of Person?
you need to tell NHibernate explicitly what is the type of your return data since you choose to select some specific properties of your original entity p.Name,P.Code these properties must by returned as of a new type of entity so the new type would be a new object
var query = SessionInstance.QueryOver<Person>()
.Where(p => p.Code == code)
.Select(
p => p.Name,
p => p.Code
)
.List<object>();
this will solve your problem
or if you defined a new entity to hold the new return data as following :
public newPeople
{
public Id { get; set; }
public Nam { get; set; }
}
you can write it like that:
var query = SessionInstance.QueryOver<Person>()
.Where(p => p.Code == code)
.Select(
p => p.Name,
p => p.Code
)
.Select( list =>
new newPeople()
{
Id = (int) list[0],
Name = (string) list[1]
})
.ToList<newPeople>();

Nhibernate join with select columns from both tables

Ok after several attempts I am stuck on this one!
I am using NHibernate with QueryOver as below. I have a Product and ProductReview as
public class Product
{
....
public virtual IList<ProductReview> CustomerReviews {get;set;}
....
}
public class ProductReview
{
....
public virtual Product Product {get;set;}
....
}
Mapping on Product side is
HasMany(x => x.CustomerReviews).KeyColumn("ProductId").Inverse().Cascade.None().LazyLoad();
The Query is
Product px = null;
ProductReview rev = null;
var result = CurrentSession
.QueryOver<ProductReview>()
.Where(r => r.IsActive && !r.IsDraft)
.Select(
Projections.Property<ProductReview>(r => r.Id).WithAlias(() => rev.Id),
Projections.Property<ProductReview>(r => r.Title).WithAlias(() => rev.Title)
)
.OrderBy(r => r.ReviewDate).Desc()
.TransformUsing(Transformers.AliasToBean<ProductReview>())
.JoinAlias(r => r.Product, () => px)
.Select(
Projections.Property(() => px.UPC).WithAlias(() => px.UPC),
Projections.Property(() => px.FullName).WithAlias(() => px.FullName)
)
.TransformUsing(Transformers.AliasToBean<Product>())
.Take(5)
.List();
The error is:
The value "Reviews.Models.Product" is not of type "Reviews.Models.ProductReview" and cannot be used in this generic collection. Parameter name: value
I really do not want to create another DTO. I would like to get the list of Last 5 new reviews and have it's Product populated (only a few required fields on both entities should be filled).
Is this possible by any means (except raw sql) in NHibernate 3.0?
Product px = null;
ProductReview rev = null;
var result = CurrentSession.QueryOver<ProductReview>()
.Where(r => r.IsActive && !r.IsDraft)
.JoinQueryOver(r => r.Product)
.OrderBy(r => r.ReviewDate).Desc()
.Take(5)
.List();

NHibernate selecting from a many to many collection

I would like NHibernate to generate the following SQL but I'm stuggling with the code below
SELECT rt.Id
FROM ClientContact cc
JOIN ClientContact_DefaultRequest d on d.ClientContactID = cc.Id
JOIN RequestType rt on d.RequestTypeId = rt.Id
WHERE
cc.Id = ContactId
public class GetDefaultRequestsForContact : SimpleQuery<IEnumerable<RequestType>>, IGetDefaultRequestsForContact
{
public int ContactId { private get; set; }
public GetDefaultRequestsForContact(ISession session) : base(session) { }
public override IEnumerable<RequestType> Execute()
{
var x = Session.QueryOver<ClientContact>()
.Where(c => c.Id == ContactId)
.JoinQueryOver<RequestType>(c => c.DefaultRequests)
.Select(c => c.DefaultRequests)
.List();
return null;
}
}
The generated SQL is only selecting the parent id (which is the parameter) I want all the child Ids.
SELECT this_.Id as y0_
FROM ClientContact this_
inner join ClientContact_DefaultRequest defaultreq3_
on this_.Id = defaultreq3_.ClientContactID
inner join RequestType requesttyp1_
on defaultreq3_.RequestTypeID = requesttyp1_.Id
WHERE this_.Id = 313706 /* #p0 */
This works but it is not strongly typed (HQL).
var x = Session.CreateQuery("SELECT R.Id FROM ClientContact cc JOIN cc.DefaultRequests R WHERE cc.Id = :contactId")
.SetParameter("contactId",ContactId)
.List<int>();
return x;
Try using Aliases like:
ClientContact Cont = null;
RequestType Req = null;
var x = session.QueryOver<ClientContact>(() => Cont)
.Where(() => Cont.ID == ContactId)
.JoinAlias(() => Cont.DefaultRequests, ()=> Req, JoinType.LeftOuterJoin)
.Select(ignore => Req.Id) // <-- Select wants a parameter
.List<int>();
You have to turn it around. I believe Faber's answer will also work well. So you do not have to turn it around. (But you could.)
ClientContact cAlias = null;
var x = Session.QueryOver<RequestType>()
.JoinAlias(rt => rt.ClientContacts, () => cAlias)
.Where(() => cAlias.Id == ContactId)
.Select(rt => rt.Id) // not sure what you want returned here, Id or the object)
.List();