views:

572

answers:

3

Hi,

I have an "Item" class, and this class has a collection "Tags".

Item
    IList<string> Tags;

my DB looks like this:

Items
   Id

Tags
   ItemId
   TagName

I am trying to get all Items which have the tags "x" and "y". How can I do this with NHibernate (preferably with criteria API)? Is it even possible?

Thanks.

EDIT: can I do it without mapping the Tag object? It doesn't have ti be 1 query. Something like

  1. var q = query that will return all id's of objects that have tag x or tag y".

  2. var res = query that will return all Items with Id in ( q.Execute())

+2  A: 

Try something like this:

session.CreateCriteria(typeof(Item))
    .CreateCriteria("Tags", global::NHibernate.SqlCommand.JoinType.InnerJoin)
    .Add(Expression.Eq("TagName", "x"))
    .Add(Expression.Eq("TagName", "y"))
    .List()

EDIT: Make sure you have a bi-directional association between Item and Tag. In Fluent, this would look something like:

public class ItemMap : ClassMap<Item>
{
    public ItemMap()
    {
        ...
        HasMany(x => x.Tags).Inverse();
        ...
    }
}

public class TagMap : ClassMap<Tag>
{
    public TagMap()
    {
        ...
        References<Item>(x => x.Item);
        ...
    }
}
Stuart Childs
It didn't work. I get NHibernate.MappingException: collection was not an association: Item.Tags
Alex Reitbort
See gcores' comment and then my edit for an example.
Stuart Childs
I know how to do it with mapped Tag object, I was hoping there is a way to do it without mapping tag string to object.
Alex Reitbort
AFAIK, the Criteria API can only work with mapped properties. You could probably do it with a session.CreateSQLQuery() though.
Stuart Childs
I'd like to have a clarification regarding the References(...) in the TagMap. How does it help if one wants to have Item.Tags return the list of tags for a given item, and Tag.Items return the list of items with this specific tag? As far as i see it, what we have here is one tag created for each item, even if it's the same tag.Isn't it equivalent to what James Gregory says here: http://stackoverflow.com/questions/432809/fluent-nhibernate-questionThank you
samy
A normal Tag relationship is many-to-many (i.e. items can be tagged as many things and each tag can be assigned to many items), at which point you need a Tag class and need it mapped. *Every* time I've helped someone with tags they intended many-to-many but started with one-to-many so I probably just instinctively nudge people towards mapping Tag now. If "tag" is a simplification or you *truly* mean to have one-to-many, then yeah you can use an element collection. References() in this case simply makes the relationship bidirectional.
Stuart Childs
+1  A: 

I think the problem your getting is associated with this: https://www.hibernate.org/117.html#A2 - i.e. collections of strings/components can't be queried this way using the criteria API.

I worked around the problem using HQL, as stated in the linked FAQ:

session.CreateQuery("from Item item "
    + "where :x in elements(item.Tags) and :y in elements(item.Tags)")
    .SetString("x", X)
    .SetString("y", Y);

It appears to work as intended.

Liedman
A: 

I ran across the same problem recently and really didn't find a good solution anywhere for my particular problem. In my solution I needed a mapped Tags object since it was a little more complicated. I'll describe what I did in case it helps anyone. Essentially this is the equivalent of the following sql:

SELECT * 
FROM Items I 
WHERE 2 = (
    SELECT COUNT(DISTINCT TagName) 
    FROM Tags T 
    WHERE T.ItemId = I.ID) 
      AND (T.TagName = 'X' OR T.TagName = 'Y')
)



NHibernate.ICriteria criteria = session.CreateCriteria(typeof(Item),"I");

ICriterion tagCriteria = null;
foreach (string tag in tagNames) {
    ICriterion newCriteria = Expression.Eq("TagName", tag);
    if (tagCriteria == null) {
        tagCriteria = newCriteria;
    } else {
        tagCriteria = Expression.Or(tagCriteria, newCriteria);
    }
}
if (tagCriteria != null) {
    DetachedCriteria subCriteria = DetachedCriteria.For<Tags>("T");
    subCriteria.SetProjection(Projections.CountDistinct("TagName"))
        .Add(Expression.EqProperty("I.Id", "T.ItemId"))
        .Add(tagCriteria);
    criteria = criteria.Add(Subqueries.Eq(tagNames.Count,subCriteria ));
}
return criteria.List<Item>();
Mike Hesler