Getting duplicate results in query - nhibernate

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.

Related

Many to many query joins in aqueduct

I have A -> AB <- B many to many relationship between 2 ManagedObjects (A and B), where AB is the junction table.
When querying A from db, how do i join B values to AB joint objects?
Query<A> query = await Query<A>(context)
..join(set: (a) => a.ab);
It gives me a list of A objects which contains AB joint objects, but AB objects doesn't include full B objects, but only b.id (not other fields from class B).
Cheers
When you call join, a new Query<T> is created and returned from that method, where T is the joined type. So if a.ab is of type AB, Query<A>.join returns a Query<AB> (it is linked to the original query internally).
Since you have a new Query<AB>, you can configure it like any other query, including initiating another join, adding sorting descriptors and where clauses.
There are some stylistic syntax choices to be made. You can condense this query into a one-liner:
final query = Query<A>(context)
..join(set: (a) => a.ab).join(object: (ab) => ab.b);
final results = await query.fetch();
This is OK if the query remains as-is, but as you add more criteria to a query, the difference between the dot operator and the cascade operator becomes harder to track. I often pull the join query into its own variable. (Note that you don't call any execution methods on the join query):
final query = Query<A>(context);
final join = query.join(set: (a) => a.ab)
..join(object: (ab) => ab.b);
final results = await query.fetch();

Magento: Get Collection of Order Items for a product collection filtered by an attribute

I'm working on developing a category roll-up report for a Magento (1.6) store.
To that end, I want to get an Order Item collection for a subset of products - those product whose unique category id (that's a Magento product attribute that I created) match a particular value.
I can get the relevant result set by basing the collection on catalog/product.
$collection = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToFilter('unique_category_id', '75')
->joinTable('sales/order_item', 'product_id=entity_id', array('price'=>'price','qty_ordered' => 'qty_ordered'));
Magento doesn't like it, since there are duplicate entries for the same product id.
How do I craft the code to get this result set based on Order Items? Joining in the product collection filtered by an attribute is eluding me. This code isn't doing the trick, since it assumes that attribute is on the Order Item, and not the Product.
$collection = Mage::getModel('sales/order_item')
->getCollection()
->join('catalog/product', 'entity_id=product_id')
->addAttributeToFilter('unique_category_id', '75');
Any help is appreciated.
The only way to make cross entity selects work cleanly and efficiently is by building the SQL with the collections select object.
$attributeCode = 'unique_category_id';
$alias = $attributeCode.'_table';
$attribute = Mage::getSingleton('eav/config')
->getAttribute(Mage_Catalog_Model_Product::ENTITY, $attributeCode);
$collection = Mage::getResourceModel('sales/order_item_collection');
$select = $collection->getSelect()->join(
array($alias => $attribute->getBackendTable()),
"main_table.product_id = $alias.entity_id AND $alias.attribute_id={$attribute->getId()}",
array($attributeCode => 'value')
)
->where("$alias.value=?", 75);
This works quite well for me. I tend to skip going the full way of joining the eav_entity_type table, then eav_attribute, then the value table etc for performance reasons. Since the attribute_id is entity specific, that is all that is needed.
Depending on the scope of your attribute you might need to add in the store id, too.

How to simplify this LINQ to Entities Query to make a less horrible SQL statement from it? (contains Distinct,GroupBy and Count)

I have this SQL expression:
SELECT Musclegroups.Name, COUNT(DISTINCT Workouts.WorkoutID) AS Expr1
FROM Workouts INNER JOIN
Series ON Workouts.WorkoutID = Series.WorkoutID INNER JOIN
Exercises ON Series.ExerciseID = Exercises.ExerciseID INNER JOIN
Musclegroups ON Musclegroups.MusclegroupID = Exercises.MusclegroupID
GROUP BY Musclegroups.Name
Since Im working on a project which uses EF in a WCF Ria LinqToEntitiesDomainService, I have to query this with LINQ (If this isn't a must then pls inform me).
I made this expression:
var WorkoutCountPerMusclegroup = (from s in ObjectContext.Series1
join w in ObjectContext.Workouts on s.WorkoutID equals w.WorkoutID
where w.UserID.Equals(userid) && w.Type.Equals("WeightLifting")
group s by s.Exercise.Musclegroup into g
select new StringKeyIntValuePair
{
TestID = g.Select(n => n.Exercise.MusclegroupID).FirstOrDefault(),
Key = g.Select(n => n.Exercise.Musclegroup.Name).FirstOrDefault(),
Value = g.Select(n => n.WorkoutID).Distinct().Count()
});
The StringKeyIntValuePair is just a custom Entity type I made so I can send down the info to the Silverlight client. Also this is why I need to set an "TestID" for it, as it is an entity and it needs one.
And the problem is, that this linq query produces this horrible SQL statement:
http://pastebay.com/144532
I suppose there is a better way to query this information, a better linq expression maybe. Or is it possible to just query with plain SQL somehow?
EDIT:
I realized that the TestID is unnecessary because the other property named "Key" (the one on which Im grouping) becomes the key of the group, so it will be a key also. And after this, my query looks like this:
var WorkoutCountPerMusclegroup = (from s in ObjectContext.Series1
join w in ObjectContext.Workouts on s.WorkoutID equals w.WorkoutID
where w.UserID.Equals(userid) && w.Type.Equals("WeightLifting")
group w.WorkoutID by s.Exercise.Musclegroup.Name into g
select new StringKeyIntValuePair
{
Key = g.Key,
Value = g.Select(n => n).Distinct().Count()
});
This produces the following SQL: http://pastebay.com/144545
This seems far better then the previous sql statement of the half-baked linq query.
But is this good enough? Or this is the boundary of LinqToEntities capabilities, and if I want even more clear sql, I should make another DomainService which operates with LinqToSQL or something else?
Or the best way would be using a stored procedure, that returns Rowsets? If so, is there a best practice to do this asynchronously, like a simple WCF Ria DomainService query?
I would like to know best practices as well.
Compiling of lambda expression linq can take a lot of time (3–30s), especially using group by and then FirstOrDefault (for left inner joins meaning only taking values from the first row in the group).
The generated sql excecution might not be that bad but the compilation using DbContext which cannot be precompiled with .NET 4.0.
As an example 1 something like:
var q = from a in DbContext.A
join b ... into bb from b in bb.DefaultIfEmtpy()
group new { a, b } by new { ... } into g
select new
{
g.Key.Name1
g.Sum(p => p.b.Salary)
g.FirstOrDefault().b.SomeDate
};
Each FirstOrDefault we added in one case caused +2s compile time which added up 3 times = 6s only to compile not load data (which takes less than 500ms). This basically destroys your application's usability. The user will be waiting many times for no reason.
The only way we found so far to speed up the compilation is to mix lambda expression with object expression (might not be the correct notation).
Example 2: refactoring of previous example 1.
var q = (from a in DbContext.A
join b ... into bb from b in bb.DefaultIfEmtpy()
select new { a, b })
.GroupBy(p => new { ... })
.Select(g => new
{
g.Key.Name1
g.Sum(p => p.b.Salary)
g.FirstOrDefault().b.SomeDate
});
The above example did compile a lot faster than example 1 in our case but still not fast enough so the only solution for us in response-critical areas is to revert to native SQL (to Entities) or using views or stored procedures (in our case Oracle PL/SQL).
Once we have time we are going to test if precompilation works in .NET 4.5 and/or .NET 5.0 for DbContext.
Hope this helps and we can get other solutions.

Return class from nested collection using 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.

multiple results in a single call

When paging data, I want to not only return 10 results, but I also want to get the total number of items in all the pages.
How can I get the total count AND the results for the page in a single call?
My paged method is:
public IList GetByCategoryId(int categoryId, int firstResult, int maxResults)
{
IList<Article> articles = Session.CreateQuery(
"select a from Article as a join a.Categories c where c.ID = :ID")
.SetInt32("ID", categoryId)
.SetFirstResult(firstResult)
.SetMaxResults(maxResults)
.List<Article>();
return articles;
}
The truth is that you make two calls. But a count(*) call is very, very cheap in most databases and when you do it after the main call sometimes the query cache helps out.
Your counter call will often be a little different too, it doesn't actually need to use the inner joins to make sense. There are a few other little performance tweaks too but most of the time you don't need them.
I believe you actually can do what you ask. You can retieve the count and the page in one go in code but not in one SQL statement. Actually two queries are send to the database but in one round trip and the results are retrieved as an array of 2 elements. One of the elements is the total count as an integer and the second is an IList of your retrieved entities.
There are 2 ways to do that:
MultyQuery
MultiCriteria
Here is a sample taken from the links below:
IList results = s.CreateMultiQuery()
.Add("from Item i where i.Id > :id")
.Add("select count(*) from Item i where i.Id > :id")
.SetInt32("id", 50)
.List();
IList items = (IList)results[0];
long count = (long)((IList)results[1])[0];
Here is more info on how you can do that. It is really straight forward.
http://ayende.com/Blog/archive/2006/12/05/NHibernateMutliQuerySupport.aspx
http://ayende.com/Blog/archive/2007/05/20/NHibernate-Multi-Criteria.aspx
If you read the 2 articles above you will see that there is a performance gain of using this approach that adds value to the anyway more transparent and clear approach of doing paging with MultiQuery and MultiCriteria against the conventional way.
Please note that recent versions of NHibernate support the idea of futures, so you can do.
var items = s.CreateQuery("from Item i where i.Id > :id")
.SetInt32("id", 50)
.Future<Item>();
var count = s.CreateQuery("select count(*) from Item i where i.Id > :id")
.SetInt32("id", 50)
.FutureValue<long>();
This is a much more natural syntax, and it will still result in a single DB query.
You can read more about it here:
http://ayende.com/Blog/archive/2009/04/27/nhibernate-futures.aspx