Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NHibernate How do I query against an IList<string> property?

I am trying to query against an IList<string> property on one of my domain classes using NHibernate. Here is a simple example to demonstrate:

public class Demo
{
    public Demo()
    {
        this.Tags = new List<string>();
    }
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<string> Tags { get; set; }
}

Mapped like this:

<class name="Demo">
<id name="Id" />
<property name="Name" />
<bag name="Tags">
  <key column="DemoId"/>
  <element column="Tag" type="String" />
</bag>

And I am able to save and retrieve just fine. Now to query for instances of my domain class where the Tags property contains a specified value:

var demos = this.session.CreateCriteria<Demo>()
            .CreateAlias("Tags", "t")
            .Add(Restrictions.Eq("t", "a"))
            .List<Demo>();

Results in the error: collection was not an association: Demo.Tags

var demos = (from d in this.session.Linq<Demo>()
                     where d.Tags.Contains("a")
                     select d).ToList();

Results in the error: Objct reference not set to an instance of an object.

var demos = this.session.CreateQuery("from Demo d where :t in elements(d.Tags)")
            .SetParameter("t", "a")
            .List<Demo>();

Works fine, but as my real domain class has many many properties, and I am building a complicated dynamic query, doing ugly string manipulation is not my first choice. I'd much rather use ICriteria or Linq. I have a user interface where many different possible search criteria can be entered. The code that builds up the ICriteria right now is dozens of lines long. I'd really hate to turn that into HQL string manipulation.

like image 697
JohnRudolfLewis Avatar asked Jul 30 '09 21:07

JohnRudolfLewis


3 Answers

So because of limitations of the Criteria API, I decided to bend my domain classes to fit.

I created an entity class for the Tag. I couldn't even create it as a value object. It had to have its own id.

I feel dirty now. But being able to construct a dynamic query without resorting to string manipulation was more important to me than staying true to the domain.

like image 61
JohnRudolfLewis Avatar answered Sep 30 '22 12:09

JohnRudolfLewis


As documented here:

17.1.4.1. Alias and property references

we can use:

...
A collection key             {[aliasname].key}      ORGID as {coll.key}
The id of an collection      {[aliasname].id}       EMPID as {coll.id}
The element of an collection {[aliasname].element}  XID as {coll.element}
...

there is a small bug in doc... instead of ".element" we have to use ".elements"

var demos = this.session.CreateCriteria<Demo>()
        .CreateAlias("Tags", "t")

        // instead of this
        // .Add(Restrictions.Eq("t", "a"))

        // we can use the .elements keyword
        .Add(Restrictions.Eq("t.elements", "a"))

        .List<Demo>();
like image 23
Radim Köhler Avatar answered Sep 30 '22 13:09

Radim Köhler


You need to use SubCriterias not alias. This should work:

var demos = this.session.CreateCriteria<Demo>()
            .CreateCriteria("Tags")
            .Add(Restrictions.Eq("Tag", "a"))
            .List<Demo>();
like image 23
zoidbeck Avatar answered Sep 30 '22 12:09

zoidbeck