Return class from nested collection using NHibernate - nhibernate

Doman:
class Action
Products: IList of class ActionProducts:
Category: class Category
Products: IList of class Product
Now, I want this:
var products = from a in Session.Linq<Action>()
from ap in a.Products
from p in ap.Category.Products
where a.Name == name
select p;
And this Linq actually works but: 1. produces select for all tables instead of only Products 2. produces left outer joins, not inner 3. Distinct() on the query doesn't work (though ToList().Distinct() works).
Also can be done with SelectMany(a => a.Products).SelectMany(ap => ap.Category.Products) but it doesn't work at all with current NHibernate.Linq.
So I want to use ICriteria. But I can't see how do I return product, not action?
ICriteria criteria = Session.CreateCriteria(typeof(Action))
.Add(Expression.Eq("Name", name))
.CreateAlias("Products", "ap")
.CreateAlias("ap.Category.Products", "p")
.SomehowReturnMeOnly("p");
So how do I SomehowReturnMeOnly("p")? So that I can do
return criteria.List<Product>();
which will fail because ICriteria selects Actions, not Products?
I may consider HQL but I actually doesn't like string queries... Just for example, here's the HQL that works and produces exactly the SQL that I need:
IQuery query = Session.CreateQuery("select distinct p from Action a inner join a.Products as ap inner join ap.Category.Products as p");
return query.List<Product>();

Now, something similar can be done (keeping in mind that CreateAlias can only do 1 level) using
DetachedCriteria dq = DetachedCriteria.For<Action>()
.Add(Expression.Eq("Name", name))
.CreateAlias("Products", "ap")
.CreateAlias("ap.Category", "c")
.CreateAlias("c.Products", "p")
.SetProjection(Projections.Property("p.Id"));
ICriteria criteria = Session.CreateCriteria(typeof(Product))
.Add(Subqueries.PropertyIn("Id", dq));
return criteria.List<Product>();
This works and passes test, but produces "SELECT FROM products WHERE id in (subquery)" which may be even better (no DISTINCT required) but is not what I wanted to achieve. Seems like Criteria API is very, very restrictive. So we have:
HQL with string-query drawbacks
Criteria API with lots of restrictions and sometimes awful code to achieve simple results
NH-Linq which looks very promising but is incomplete now.
So I guess I'll stick with HQL until Linq is ready.

You need to use Projections, something like this:
ICriteria criteria = Session.CreateCriteria(typeof(Action))
.Add(Expression.Eq("Name", name))
.CreateAlias("Products", "ap")
.CreateAlias("ap.Category.Products", "p")
.SetProjection(Projections.Property("ap.Category.Products"))
.List<Product>();
Have a look at the nhibernate docs here for some examples.

Well, after thinking about Chris' answer... I tried this and it seemed to work:
ICriteria criteria = Session.CreateCriteria(typeof(Action))
.Add(Expression.Eq("Name", name))
.CreateAlias("Products", "ap")
.CreateAlias("ap.Category", "c")
.SetProjection(Projections.Distinct(Projections.Property("c.Products")));
Looks like NHibernate doesn't allow deep nesting of projection properties which is strange. And it doesn't work either, looking at generated SQL I see that it only selects
SELECT distinct c2_.Id as y0_ FROM ... Categories c2_ ...
i.e. it doesn't really fetch products, which makes my unit test to fail because returned list contains only nulls instead of Product instances.

Related

How to perform joins in NHibernate with paging

I'm performing some maintenance on some code that uses NHibernate as a person who knows almost nothing about NHibernate...
I have the following query
var query = string.Format(#"select s.Id, s.Iccid, c.Name as Carrier, aa.StartDate as AssignmentDate, cust.Name as AssignedCustomerName
from assetassignment aa
left join SIM s on aa.AssetId = s.Id
left join Carrier c on s.CarrierId = c.Id
left join customer cust on aa.CustomerId = cust.Id
where aa.enddate is null
and aa.CustomerId in ({0})
and s.dateremoved is null",
string.Join(",",idsToInclude));
if (!string.IsNullOrWhiteSpace(carrier))
{
query += " and c.Name = '" + carrier + "'";
}
var results = _session.CreateSQLQuery(query)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(HomepageSIMTableRow)))
.List<HomepageSIMTableRow>();
return results;
This works fine for me (and means I didn't have to grok NHibernate to get something running I could work against but now I need to add paging and it is just feeling smelly.
Any guidance on how to move this into NHibernate land and add paging would be awesome!
I'm not sure if this works with regular SQL, but usually with NHibernate you add a
var results = _session.CreateSQLQuery(query)
.SetFirstResult(0)
.SetSetMaxResults(30)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(HomepageSIMTableRow)))
.List<HomepageSIMTableRow>();
This works for regular Criterias and HQL queries.
You can read this as a reference: How can you do paging with NHibernate?
The reason this feels "smelly" is because you're writing SQL and passing it straight to the ORM.
NH offers a whole mechanism for paging at an entity level. I have found this to get a little tricky when you're eagerly loading other entities though.
My suggestion would be to either:
Write the pagination SQL yourself, this is probably lower risk as it will involve less changes
Convert the whole query to use NH ICriterion query or a HQL statement.
Unfortunately it's hard to suggest which one without knowing the risk/situation.

nHibernate QueryOver and JoinQueryOver

I'm reasonably new to nHibernate and I've been trying to write a query. I cant seem to get it right. I have in my model, a "Product" which contains a ICollection of "Component". I need to find all products that contain any components that have a component reference starting with the letter "G". I have tried this:
var matching = session.QueryOver<Product>()
.JoinQueryOver<Component>(p => p.Components)
.Where(c => c.ComponentReference.StartsWith("G")).List();
However I am getting a compile error saying 'Delegate System.Func>> does not take 1 parameter.
There is an overload on JoinQueryOver where I can pass in an Expression>>
So I would have thought my query would work since ICollection implements IEnumerable.
I have tried various other ways using .Cast and JoinAlias but they just seem unecessarily complicated.
Can anyone point out where I am going wrong?
Thanks in advance
I would suggest to use the subquery, in this case. It could look like this
Product product = null;
Component component = null;
// the subselect, returning the Product.ID
// only if the Component.ComponentReference is like G%
var subQuery = QueryOver.Of<Component>(() => component)
.WhereRestrictionOn(() => component.ComponentReference)
.IsLike("G", MatchMode.Start)
.Select(c => c.Product.ID);
// the root Product
var query = session.QueryOver<Product>(() => product)
.WithSubquery
.WhereProperty(() => product.ID)
.In(subQuery);
// and finally the list
var list = query
.List<Product>();
The resulting SQL would be like this:
SELECT product
FROM product
WHERE productId IN (SELECT ProductId FROM Component WHERE ComponenentReferece LIKE 'G%')
And why to use subquery instead of JOIN? because the join would in this case result in Carthesian product. The set of products returned would be multiplied by all the components starting with G%
The subquery, will result in pure, flat set of Products, so we can correctly use paging over it (.Take() .Skip())

Getting duplicate results in query

I have Three tables A, B, and C. A is a parent table with mutiple child records in B and C.
When I query into A I am getting too many records, as though FNH is doing a Cartesian product.
My query is of the form:
var list = session.Query<A>()
.Fetch(a=> a.Bs)
.Fetch(a=> a.Cs)
Where Bs is the IList property of A, and Cs is the IList property of A.
I should get only as many Bs as relate to A, and only as many Cs relate to A. Instead I get BxC elements of each.
Is there a better way to load these? I am pretty sure I avoided this exact issue in the past, but don't see it in my old example code.
I'm not sure if this is a NH bug or a mapping issue, however the query could be optimised to
session.Query<A>()
.Fetch(a=> a.Bs)
.ToFuture();
var results = session.Query<A>()
.Fetch(a=> a.Cs)
.ToFuture()
.ToList();
You could use a Transformer to get a distinct result:
var list = session.Query<A>()
.Fetch(a=> a.Bs)
.Fetch(a=> a.Cs)
.SetResultTransformer( Transformers.DistinctRootEntity )
This is NH3.2 syntax, for 2.1 you need to use new DistinctRootEntityTransformer() (I think) as parameter to SetResultTransformer instead.

Linq to Nhibernate 3.1 Group By (Or distinct)

I need to do a simple "group by" with a nhibernate query.
My try were :
(from adn in session.Query<Table>()
orderby adn.Data
select adn.Data).Distinct().ToList<string>();
and
session.Query<Table().GroupBy(adn => adn.Data).Select(dat => dat).ToList<string>()
can someone help me to find a solution ?
My goal is to retrieve all the Distinct "Data" Column.
It can be by group by or distinct.
(the 2 solutions would be better for my progression in nhibernate)
Regards
edit
Danyolviax, I tryied your solution
return (from adn in session.Query<Table>()
group adn by adn.Data into dataGroupe
select dataGroupe).ToList<string>();
It doesn't work.
I feared that I use Nhibernate 3.1 (and not 2.1)
Any correction or alternative solution ?
edit 2
In the second solution which works (^^)
I have a list of a class with one property.
In my case, I prefer to have a list of string or int.
Otherwise I should create a specific class for this case.
Do you know a trick.
And thanks for your support.
Look here:
Linq to NHibernate and Group By
here some examples:
http://msdn.microsoft.com/en-us/vcsharp/aa336754
updated
try this with 3.1.0.4000:
var ret = (from adn in session.Query<Table>()
group adn by adn.Data into dataGroup
select new {dataGroup.Key }).ToList();
update
var ret = (from adn in session.Query<Table>()
group adn by adn.Data into dataGroup
select dataGroup.Key).ToList<string>();

NHibernate Criteria Transform Result

I have an SecurityGroup entity witch has Memebers and Application properties.
Application is a lookup.
So securityGroups is in many-to-many relationship with User table and one-to-many with LookupApplciation (FK)
Now I want to select all application linked to a specific user.
I have follow criteria:
public IList<LookupApplication> GetApplicationByUser(User user)
{
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>();
}
It trows an exception
The value "Edi.Advance.Core.Model.Security.SecurityGroup" is not of type "Edi.Advance.Core.Model.Lookups.LookupApplication" and cannot be used in this generic collection.
Parameter name: value
and it is right.
How can I transform the result to IList<LookupApplication>?
Thanks
You can only return the type which you create the criteria from.
The easiest way starting from the code you have will be:
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<SecurityGroup>()
// get the lookup applications in memory
.SelectMany(x => x.LookupApplications);
This loads all SecurityGroups into memory, even if it only needs the LookupApplications. This might not be an issue when you need them anyway or when they are small.
You could also reverse the query and start from the LookupApplication
return
this.Session.CreateCriteria(typeof(LookupApplication), "la")
// requires navigation path from SecurityGroup to LookupApplication
.CreateCriteria("la.SecurityGroup", "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>()
Or use HQL, which has some features not available in Criteria, items gets all the items from a collection:
select sg.LookupApplications.items
from SecurityGroup sg inner join sg.Members u
where u.Id = :userId
HQL is actually recommended when you don't have dynamic queries.
Update, from isuruceanu's comment:
Session
.CreateQuery(
#"select sg.Application
from SecurityGroup sg
inner join sg.Members u
where u.Id = :userId")
.SetParameter("userId", user.Id)
.List<LookupApplication>();
It depends on how the SecurityGroup looks like and how LookupApplication looks like.
You could use ResultTransformer like:
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();
Granted that SecurityGroup has properties matchinig LookupAppliaction, or else you have to project those properties like:
.SetProjection(NHibernate.Criterion.Projections.ProjectionList()
.Add(Projections.Property("Number"), "OrderNumber")
.Add(Projections.Property("CreatedOn"), "CreatedOn")
.Add(Projections.Property("MemeberName"), "Name"))
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();