NHibernate Return Values - nhibernate

I am currently working on a project using NHiberate as the DAL with .NET 2.0 and NHibernate 2.2.
Today I came to a point where I had to join a bunch of entities/collections to get what I want. That is fine.
What got me was that I do not want the query to return a list of objects of a certain entity type but rather the result would include various properties from different entities.
The following query is not what I am doing but it is kind of query that I am talking about here.
select order.id, sum(price.amount), count(item)
from Order as order
join order.lineItems as item
join item.product as product,
Catalog as catalog
join catalog.prices as price
where order.paid = false
and order.customer = :customer
and price.product = product
and catalog.effectiveDate < sysdate
and catalog.effectiveDate >= all (
select cat.effectiveDate
from Catalog as cat
where cat.effectiveDate < sysdate
)
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc
My question is, in this case what type result is supposed to be returned? It is certainly not of type Order, neither is of type LineItems.
Thanks for your help!
John

you can always use List of object[] for returning data and it will work fine.

This is called a projection, and it happens any time you specify an explicit select clause that contains rows from various tables (or even aggregate / summary data from a single table).
Using LINQ you can create anonymous objects to store these rows of data, like this:
var crunchies = (from foo in bar
where foo.baz == quux
select new { foo.corge, foo.grault }).ToList();
Then you can do crunchies[0].corge for example to pull out the rows & columns.
If you are using NHibernate.Linq this will "just work".
If you're using HQL or Criteria API, then what Fahad mentioned will work. You'll get a List<object[]> as a result, and the index of the array references the order of the columns that you returned in your select clause.

Related

Linq Group By Sum based on a condition

I have this SQL query but can't figure out how to convert it into VB.net LINQ
SELECT
clientid,
SUM(IF(`type` = 1, cost, 0)) AS advexp,
SUM(IF(`type` = 3, cost, 0)) AS genexp
FROM customerlist
WHERE clGroupId=pGroupId
GROUP BY clientid;
Table customerlist includes a field type and a field cost. The intent is to get a total of type 1 and type 3 expenses in one query.
This is what I tried:
From p In db.customerlist
Where p.clGroupId=1
Group By p.clientid,p.type Into g
Select New With { .clientid=g.clientd,.advexp=g.Sum(Function(c) If(c.type=1, cost,0)),.genexp=g.Sum(Function(c) If(c.type=3, cost,0))}
But not able to compile. The query is intended to hit the database. The error is Definition of method 'g' is not accessible in this context.
The question doesn't explain whether an ORM is used or what customerlist is. A list of customers in memory, or a DbSet ?
If this was a List, the LINQ query would be almost identical to the SQL query :
from customer in customerlist
where p.clGroupId=pGroupId
group by p.clientid into g
select new { clientid=g.Key,
advexp=g.Sum(c=>c.type==1?cost:0),
genexp=g.Sum(c=>c.type==3?cost:0)
}
Depending on the ORM and the database provider used, EF or NHibernate may be able to translate the same query into SQL

Core Data SUBQUERY with key-path collection causes SQL error

I'm experiencing a SQL error from a Core Data-generated query. Here's the data model:
I want to get all of the drivers for whose busses have active passengers. This is simply described by the following fetch request and predicate, which works:
NSFetchRequest(entityName: "Driver")
NSPredicate(format: "ANY bus.passengers.active == YES")
However, let's say I want to do a more complex query on the passenger, like they are both active and a balance above 100. To do such a query, I'd have to use a SUBQUERY, like so:
NSPredicate(format: "SUBQUERY(bus.passengers, $passenger, $passenger.active == YES
AND $passenger.balance > 100).#count > 0")
I would expect SUBQUERY to accept a keypathed collection as its first argument. It generates the following SQL query:
SELECT 0,
t0.z_pk,
t0.z_opt,
t0.zbus
FROM zdriver t0
WHERE (SELECT Count(t2.z_3passengers)
FROM zbus t1
JOIN z_1passengers t2
ON t1.z_pk = t2.z_1busses
WHERE ( t0.zbus = t1.z_pk
AND (( t2.zactive = ?
AND t2.zbalance > ? )) )) > ?
And the error: 'no such column: t2.ZACTIVE'. It looks like it's missing a JOIN on the ZPASSENGER table.
Is this a Core Data bug or is there a different way to write this predicate?
Update: Here's a sample project that reproduces this issue.
Try fetching the Bus entity, avoiding the key path in the first argument in the SUBQUERY.
As was pointed out, this can be viewed as a limitation or a bug.
Update:
Further research suggests that your subquery predicate should actually work. I would recommend to check your NSManagedObject subclasses and make sure passengers returns a collection and active is properly listed as an attribute.

LINQ not returning all child records

I have a query in the DB:
SELECT GreenInventoryBlendGradeID,bgx.blendgradeid,
bgX.GreenBlendGradeTypeID,[Description]
FROM [GreenInventory] gi
INNER JOIN [GreenInventoryBlendGradeXref] bgX
ON bgX.[GreenInventoryID] = gi.[GreenInventoryID]
INNER JOIN [BlendGrade] bg
ON bg.[BlendGradeID]=bgx.[BlendGradeID]
That returns 3 records:
TypeID Desc
1 XR
2 XR
1 XF2
The LINQ:
var GreenInventory = (from g in Session.GreenInventory
.Include("GreenInventoryBlendGradeXref")
.Include("GreenInventoryBlendGradeXref.BlendGrade")
.Include("GreenInventoryBlendGradeXref.GreenBlendGradeType")
.Include("GreenInventoryWeightXref")
.Where(x => x.GreenInventoryID == id && x.GreenInventoryBlendGradeXref.Any(bg=>bg.GreenBlendGradeTypeID > 0) )
select g);
I have tried different Where clauses including the simple - (x => x.GreenInventoryID == id)
but always have only the first 2 records returned.
Any Ideas?
If I try the following:
var GreenInventory = (from gi in Session.GreenInventory.Where(y => y.GreenInventoryID == id)
join bgX in Session.GreenInventoryBlendGradeXref.DefaultIfEmpty() on gi.GreenInventoryID equals bgX.GreenInventoryID
join bg in Session.BlendGrade.DefaultIfEmpty() on bgX.BlendGradeID equals g.BlendGradeID
select new { GreenInventory = gi, GreenInventoryBlendGradeXref = bgX, BlendGrade = bg });
I Get back 3 of each objects and the correct information is in the BlendGrade objects. It looks like the 3 GreenInventory objects are the same. They each include 2 of the GreenInventoryBlendGradeXref objects which show the the same 2 records as before.
So I not clear on what the original problem was. Also dont know if this is the best way to resolve it.
Thanks for the answers. If anyone has a further thoughts please let us know.
Based on the few details you present, I would assume that you are missing a join. I have no experience with EntityFramework (I assume that you use this ORM), but as far as I know, the ".Include" tries to ensure that the set of root entities will not change and will not contain duplicates.
Your manually created query seems to indicate that there is at least one 1:n relationship in the model. The result you get from LINQ show that only distinct GreenInventory entities are returned.
Therefore you need to adjust your query and explicitly declare that you want all results (and not only distinct root entities) - I would assume that with an explicit join EntityFramework will yield all expected results - or you need to adjust your mapping.
The first place I'd look in would be your model and joins you have defined between the entities. You might also want to check your generated SQL statement:
Trace.WriteLine(GreenInventory.Provider.ToString())
or use Visual Studio IntelliTrace to investigate what was sent to the database.

Problem in HQL when try to get count of a property (that is entity) and its value

In my project, I have two entity, first PaperEntity contains several properties (consisting of value types and also reference types -reference to other entities-) and second is PaperStatusEntity.
PaperEntity has a property named Result of type PaperStatusEntity (and also a property named locked of type bool)
Imagine you have near 500 papers and just 8 paper status defined in database.
I want to find how much every status is used? for example status1 used 58 times and status2 used 130 times and so on.
I write below HQL
select paper.Result, Count(paper.Result) from PaperEntity paper group by paper.Result
this hql generates below error:
Column 'Conference_PaperStatusesTable.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
generated sql is:
select paperentit0_.Result as col_0_0_, count(paperentit0_.Result) as col_1_0_, paperstatu1_.Id as Id48_, paperstatu1_.Version as Version48_, paperstatu1_.CreationTime as Creation3_48_, paperstatu1_.Portal as Portal48_, paperstatu1_.TitleCodeName as TitleCod5_48_, paperstatu1_.Enabled as Enabled48_, paperstatu1_.RefereeChoice as RefereeC7_48_, paperstatu1_.OrderIndex as OrderIndex48_, paperstatu1_.ContactMessageTemplate as ContactM9_48_ from Conference_PapersTable paperentit0_ inner join Conference_PaperStatusesTable paperstatu1_ on paperentit0_.Result=paperstatu1_.Id, Conference_PaperStatusesTable paperstatu2_ where paperentit0_.Result=paperstatu2_.Id group by paperentit0_.Result
If I try to group data with a value type property like 'Locked' (that is bool), no problems and all things are ok
also If I use Criteria instead of HQL, works truly:
IList result = NHibernateSessionManager.Instance.CurrentSession.CreateCriteria(typeof(PaperEntity))
.SetProjection(Projections.ProjectionList().Add(Projections.RowCount()).Add(Projections.GroupProperty("Result"))).List();
foreach (var item in result) {
object[] value = item as object[];
yield return new Pair<PaperStatusEntity, int>(value[1] as PaperStatusEntity, (int)value[0]);
}
HQL group by is not smart enough to project all the properties of an entity when grouping by it. You have to either:
Specify all the properties
Select just those you need (id, description, whatever)
Use a subquery to get the count
The reason why it's working with Criteria is that it selects just the id when you use Projections.GroupProperty, creating uninitialized proxies.
This can create a SELECT N+1 problem.
You need to provide a simple data type for the database engine to group by. If paper.Result is another entity you'll probably need to join to the PaperStatusEntity table.
Something like:
select paper.Result, Count(paper.Result)
from PaperEntity as paper
join paper.Result as status
group by status.Id
If PaperStatus is mapped as a component you can also access Result.Id directly.
You can try:
SELECT paper.Result, Count(paper.Result)
FROM PaperEntity paper
JOIN paper.Result as status
GROUP BY paper.Result
Note that you would rather use a "FROM PaperStatusEntity" and join the papers
Because with the actual thing you are trying to do, you may not get a count for a PaperStatusEntity if no paper use this status (this means you don't get a count=0, you just get no count)
You can also use the #Formula annotation so that, for a given status, you can do paperStatusEntity.getPaperNumber()
Just add something like
#Formula("select count() from PaperEntity paper where paper.result_id = id");
public int getPaperNumber();
(should be adapted)
(I use Hibernate but it should be the same?)

HQL and grouping

After much problems with using 'group by' in linq2nhibernate, I have tried to switch to HQL, but I am struggeling with a simple example.
I have the following table (ForumThreadRatings):
I would like to retrieve a list of the highest rated forum threads, which means I need to do a sum with the positive column and a group by the forumthread. I have tried for an example just to do a simple group by in HQL with no luck:
select ftr.ForumThread from ForumThreadRating ftr group by ftr.ForumThread
But I receive the following error:
Column 'ForumThreads.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
What might I be missing?
From the docs:
NHibernate currently does not expand a grouped entity, so you can't write group by cat if all properties of cat are non-aggregated. You have to list all non-aggregated properties explicitly.
In any case, that exact query can be accomplished by:
select distinct ftr.ForumThread from ForumThreadRating ftr
But of course you probably need to sum or count something, so you'll need to explicitly aggregate the properties.
Update: here's how to get the top 10 threads:
var topThreads = session.CreateQuery(#"
select (select sum(case
when rating.Positive = true then 1
else -1
end)
from ForumThreadRating rating
where rating.ForumThread = thread),
thread
from ForumThread thread
order by 1 desc
")
.SetMaxResults(10)
.List<object[]>()
As you can see, this query returns a list of object[] with two elements each: [0] is the rating and [1] is the ForumThread.
You can get just the ForumThreads using:
.Select(x => (ForumThread)x[1]);
Or project them into a DTO, etc.