views:

200

answers:

3

I have an Article with a Set of Category. How can I query, using the criteria interface, for all Articles that contain all Categories with a certain Id?

This is not an "in", I need exclusively those who have all necessary categories - and others. Partial matches should not come in there.

Currently my code is failing with this desperate attempt:

var c = session.CreateCriteria<Article>("a");
if (categoryKeys.HasItems())
{
    c.CreateAlias("a.Categories", "c");
    foreach (var key in categoryKeys)
     c.Add(Restrictions.Eq("c", key)); //bogus, I know!
}
A: 

I'm not 100% sure, but I think query by example may be what you want.

Daniel Auger
Maybe this could work, but I'd prefer something a bit more obvious...
Jan Limpens
+1  A: 

Use the "IN" restriction, but supplement to ensure that the number of category matches is equal to the count of all the categories you're looking for to make sure that all the categories are matched and not just a subset.

For an example of what I mean, you might want to take a look at this page, especially the "Intersection" query under the "Toxi solution" heading. Replace "bookmarks" with "articles" and "tags" with "categories" to map that back to your specific problem. Here's the SQL that they show there:

SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3

I believe you can also represent this using a subquery that may be easier to represent with the Criteria API

SELECT Article.Id
FROM Article 
INNER JOIN (
      SELECT ArticleId, count(*) AS MatchingCategories 
      FROM ArticleCategoryMap  
      WHERE CategoryId IN (<list of category ids>)
      GROUP BY ArticleId
) subquery ON subquery.ArticleId = EntityTable.Id 
WHERE subquery.MatchingCategories = <number of category ids in list>
iammichael
Sounds very resonable, I received the same suggestion at the nhuser list. I'll see if I can put this into criteria - does not seem overly complicated.
Jan Limpens
See also: http://stackoverflow.com/questions/948978/nhibernate-query-for-matching-all-tags for a query method using HQL.
iammichael
A: 

Assuming that Article to Category is a one-to-many relationship and that the Category has a many-to-one property called Article here is a VERY dirty way of doing this (I am really not proud of this but it works)

List<long> catkeys = new List<long>() { 4, 5, 6, 7 };

if (catkeys.Count == 0)
    return;

var cr = Session.CreateCriteria<Article>("article")
  .CreateCriteria("Categories", "cat0")
  .Add(Restrictions.Eq("cat0.Id", catkeys[0]));

 if (catkeys.Count > 1)
 {
  for (int i = 1; i < catkeys.Count; i++)
  {
   cr = cr.CreateCriteria("Article", "a" + i)
          .CreateCriteria("Categories", "cat" + i)
          .Add(Restrictions.Eq("cat" + i + ".Id", catkeys[i]));
  }
 }

var results = cr.List<Article>();

What it does is to re-join the relationship over and over again guaranteeing you the AND between category Ids. It should be very slow query especially if the list of Ids gets big.

I am offering this solution as NOT a recommended way but at least you can have something working while looking for a proper one.

tolism7