How to query a many-to-many collection with NHibernate? - nhibernate

I've been trying to interpret the answers to similar questions but haven't been able to make it work.
I have a list of activities, and each activity as a list of participants. Here are the mappings:
<class name="Activity" lazy="false">
<id name="ID">
<generator class="guid" />
</id>
<list name="Participants">
<key column="Activity" />
<index column="Ord" />
<many-to-many column="Contact" class="Model.Contact" />
</list>
<property name="Timestamp" />
</class>
<class name="Contact" lazy="false">
<id name="ID">
<generator class="guid" />
</id>
<property name="Name" />
</class>
I am currently retrieving the activities ten at a time with this criteria:
var crit = ModelSession.Current.CreateCriteria<Activity>()
.AddOrder(Order.Desc("Timestamp"))
.SetFirstResult(start)
.SetMaxResults(count);
Now I need to retrieve only those activities in which a particular person participated, in other words: where Contact.Name like '%some_name%'. In raw SQL I'd probably write something like this:
select * from Activity where ID in (select p.Activity from Participants p,
Contact c where p.Contact=c.ID and c.Name like '%some_name%')
Any idea how to do this, with HQL or ICriteria, in a way that lets me keep the paged results and ordering? Many thanks!

ChssPly76 response above wasn't quite correct — this is a unidirectional relationship, so there is no "activity" property on the contacts to project — but it got me on the right track.
The correct answer is actually much simpler (in its own answer so I can format it properly):
crit.CreateCriteria("Participants")
.Add(Expression.Like("Name", like));
Added to the criteria from the original question, this says "return a page of activities, ordered by timestamp, where a participant has this name".
Taken a step further, I really wanted any activity where the search term appeared in the list of participants OR in the description of the activity. In this case you really do need the subquery.
var subquery = DetachedCriteria.For<Activity>()
.SetProjection(Projections.Property("ID"))
.CreateCriteria("Participants")
.Add(Expression.Like("Name", search_term));
crit.Add(Expression.Or(
Subqueries.PropertyIn("ID", subquery),
Expression.Like("Description", search_term));

You just need to add an appropriate condition to your criteria:
crit.CreateAlias("Participants", "participant")
.Add( Expression.Like("participant.Name", "%some_name%") );
See Criteria associations chapter of NHibernate documentation for more details
Update You can use DetachedCriteria to specify your Participant conditions as a subquery:
DetachedCriteria subquery = DetachedCriteria.For(typeof(Participant))
.SetProjection(Projections.Property("activity"))
.Add( Expression.Like("name", "%some_name%") );
crit.add( Subqueries.GeAll("ID", subquery) ).List();

Related

nHibernate - How to HQL for joined data

I am having some troubles crafting the HQL for a given nHibernate mapping file (nHibernate 1.2). This SQL Fiddle example shows the table structures and the results I'm wanting. Basically, I'll pass in a Jurisdiction and I want all Titles with no jurisdiction or that specific jurisdiction. The current process pulls everything back and filters in code; I'm working to improve this so it filters at the SQL level.
Mapping file looks very similar to this.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyApp.Business"
namespace="Ap.Bus.Entity"
default-access="field.camelcase-underscore"
default-cascade="save-update" >
<class name="Titles" table="dbo.Titles" mutable="true" lazy="true">
<!--Primary Key-->
<id column="TitleID" name="Id" unsaved-value="0">
<generator class="identity">
</generator>
</id>
<property column="TITLE" name="Title"/>
<property column="Enabled" name="Enabled" />
<bag name="Jurisdictions" table="dbo.Jurisdictions" lazy="false" cascade="none">
<key column="TitleID" />
<many-to-many class="Ap.Shared.Jurisdiction, Ap.Shared" column="JurisdictionID"/>
</bag>
</class>
I've tried so many different HQLs and I could never get it to work when I started joining to the Jurisdictions. It was working when only returning enabled Titles without the join.
So, how can I write the HQL to accomplish this?
select t
from Title t left outer join t.Jurisdictions as j
where j is null or j = :someJurisdiction
Possibly you should add ".JurisdictionID" to the last two "j" above.

NHibernate Many to Many delete all my data in the table

I would love to thank #Stefan Steinegger and #David helped me out yesterday with many-to-many mapping.
I have 3 tables which are "News", "Tags" and "News_Tags" with Many-To-Many relationship and the "News_Tags" is the link table.
If I delete one of the news records, the following mappings will delete all my news records which have the same tags. One thing I need to notice, I only allowed unique tag stored in the "Tag" table.
This mapping make sense for me, it will delete the tag and related News records, but how can I implement a tagging system with NHibernate?
Can anyone give me some suggestion? Many thanks.
Daoming.
News Mapping:
<class name="New" table="News" lazy="false">
<id name="NewID">
<generator class="identity" />
</id>
<property name="Title" type="String"></property>
<property name="Description" type="String"></property>
<set name="TagsList" table="New_Tags" lazy="false" inverse="true" cascade="all">
<key column="NewID" />
<many-to-many class="Tag" column="TagID" />
</set>
</class>
Tag Mapping:
<class name="Tag" table="Tags" lazy="false">
<id name="TagID">
<generator class="identity" />
</id>
<property name="TagName" type="String"></property>
<property name="DateCreated" type="DateTime"></property>
<!--inverse="true" has been defined in the "News mapping"-->
<set name="NewsList" table="New_Tags" lazy="false" cascade="all">
<key column="TagID" />
<many-to-many class="New" column="NewID" />
</set>
</class>
When I run into trouble like that, the first thing I twiddle with is the cascade option.
As far as I know, the mapping is correct (I'm using mapping files that look exactly the same). The problem is the cascade attribute: the "all" option forces NHibernate to propagate each action on an entity to the instances of the collection. In your case, when you delete a news item all related tags are deleted too.
You probably should use "none" (in that case you'll eventually end up with some unused tags in the database) or "delete-orphans" (on the news item side - use "none" on the tag side).
Use the cascade option "save-update".
The option "all" will cascade deletes, which you do not want in this case. But you the option "none" will require that the Tag entity is already persisted which I guess might not always be the case.
So by setting the cascade to "save-update" new Tags till be inserted in the Tags table and in the link table News_Tags, but when you remove a tag from a News entity it will only be removed from the link table not the Tags table.

NHibernate: Where clause on one-to-many relationships doesn't work when column name is ambiguous

It is possible to specify an arbitrary SQL where clause for collection mappings. For example:
<map name="myEntity" where="foo = 1" />
However if the column name is ambiguous for some reason, the sql fails. For example, this can occur if you are trying to use joins for example.
Given that the table aliases are automatically generated, you can't qualify the column name. This makes the feature seem rather silly. Does anyone know if there is a work around?
NHibernate should figure out the correct alias for the property you are referencing. Is foo a mapped property of the item entity type (the item type that is in the map collection) ?
For example this works:
<class name="Category" table="Category">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Name" not-null="true" length="255" />
<bag name="ProductList" table="Product" cascade="none" where="Name like '%test%'" fetch="join">
<key column="CategoryId" />
<one-to-many class="Product" />
</bag>
</class>
There is a property on both Category and the Product class named "Name" but nhibernate will in this case use the on defined on the Product class.

fetch=join to tables to one class NHibernate

I have read some post about fetch=join - http://nhforge.org/blogs/nhibernate/archive/2009/04/09/nhibernate-mapping-lt-many-to-one-gt.aspx (ser4ik.livejournal.com/2505.html)
So I have some question, Forexample I have class
<class name="AttributesInf" table="attr_inf">
<id name="Id">
<generator class="identity"/>
</id>
<property name="Name" />
<property name="Desc"/>
</class>
and
<class name="AttributeValue" table="attr_val">
<id name="Id">
<generator class="identity"/>
</id>
<property name="Value" />
<many-to-one name="AttrName" column="attrId"/>
</class>
If I use this mapping without set fetch="join" I get sql:
Select av.Id, av.Value, av.attrId From attr_val av where av.Id=...()
and after then separate sql queries like:
Select * From attr_inf where Id = av.attrId
So my Result Is:
class AttrinuteInf
{
int Id;
string Name;
string Desc;
}
class AttributeValue
{
int Id;
string Value;
AttributeInf AttrName;
}
If I set fetch="join" then I get one query:
Select u.Id, u.UserName, u.BlogId, b.Id, b.BlogName, b.BlogAuthor, b.BlogMsg
from Users u
Left outer join Blogs b
On u.BlogId=b.Id
Where u.Id = ...
So I expect to get one Class:
class AttributeValue
{
int Id;
string Value;
string Name;
string Desc;
}
But I have the same result as if I not set fetch to "join".
Is this all right?
Are there any way to get properties from class maped as <many-to-one> directly?
(not as AttrName.Name, but simply Name )
Explanation:
Part of mapping set above don't show my real problem.
I want to map some entity as IDictionary<string,AttributeValue>.
I map it as
<map name="Attributes" table="attr_val" lazy="true" cascade="all-delete-orphan" inverse="true">
<key column="item_id"></key>
<index column="name"></index> //I don't have that item in class AttributeValue, that is why I try to get it from other table
<one-to-many class="AttributeValue"/>
</map>
This isn't doing what you think it's doing. Using fetch=join is just there to eager load the many side of the relationship. In both cases you end up with the same objects returned. By default NHibernate will lazy load the related entities (which is why you get the second query). By using fetch=join you are asking for the entire object relationship at once but it will still populate the objects the same as without the fetch=join.
It's not the way you describe it. Your entities do not change depending on what you request.
You will obtain a list of instances of your main entity, with the association to the other being fetched. So if, in your code, you access the association, you will find the values.
If you don't fetch it, you wouldn't be able to access those fields, as they would not have been retrieved from the database.
I don't understand your question.
The 'fetch-join' attribute just defines how the sql that NHibernate generates to retrieve instances of your classes, should look like.
It has nothing to do with 'what' will be returned. NHibernate will translate the recordset (that is the result of the query) to correct instances of your classes.
If you just want to retrieve parts of your entity (like the name for instance), then you'll have to write a HQL query, or use the ICriteria API and maybe use projections.

NHibernate fetch="join" mapping attribute does not appear to work

Mapping a dictionary with NH. Declaration is as follows:
<hibernate-mapping ...
<map
name="CostsByRole"
table="JobAccountingSnapshotCosts"
lazy="false"
fetch="join"
access="nosetter.camelcase-underscore">
<key column="SnapshotId" />
<index column="RoleCode" type="String" />
<element column="Amount" type="Decimal" />
</map>
</hibernate-mapping>
I am expecting a single SQL query to be generated but instead I get two: a select for the actual object, followed by a select for the contents of the dictionary.
Any ideas?
HQL queries do not consider the values set for fetch in the mapping. You need to specify them exclusively in each HQL query. Its supposedly by design. The fetch attributes value is used only by Criteria queries and by Load/Get.
Assuming it's not a typo on submission, the problem is likely to be the join="fetch" part in your mapping. It should be fetch="join" and since the default for fetch is "select", that would yield your sequential select problem.