NHibernate: how to retrieve an entity that “has” all entities with a certain predicate in Criteria

StackOverflow https://stackoverflow.com/questions/1861537

  •  13-09-2019
  •  | 
  •  

Question

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!
}
Was it helpful?

Solution

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>

OTHER TIPS

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top