Querying with NHibernate - nhibernate

I am new to NHibernate and I am trying to learn how to query my data.
Below is the configuration xml. Only the recipe is shown.
I want to be able to query recipes by recipetitle from keywords entered
and also ingredients from ingredientname.
So you might enter "pasta wine" for example.
This is what I have tried but gives me an error.
hql = "from Recipe r " +
"left join r.Images " +
"inner join r.User " +
"inner join r.Ingredients i " +
"where i.IngredientName Like '%pasta%' OR i.IngredientName Like '%wine%' OR r.RecipeTitle Like '%pasta' OR r.RecipeTitle Like '%wine%'";
I want to eager load the collections as well.
Am I going about querying right??
I need to able to build the query string from my search criteria.
This would be easy form me in SQL.
Malcolm
<class name="Recipe" table="Recipes" xmlns="urn:nhibernate-mapping-2.2">
<id name="RecipeID" type="Int32" column="RecipeID">
<generator class="identity" />
</id>
<property name="RecipeTitle" type="String">
<column name="RecipeTitle" />
</property>
<property name="Completed" type="Boolean">
<column name="Completed" />
</property>
<property name="ModifiedOn" type="DateTime">
<column name="ModifiedOn" />
</property>
<property name="Rating" type="Double">
<column name="Rating" />
</property>
<property name="PrepTime" type="Int32">
<column name="PrepTime" />
</property>
<property name="CookTime" type="Int32">
<column name="CookTime" />
</property>
<property name="Method" type="String">
<column name="Method" />
</property>
<bag name="Images" inverse="true" cascade="all">
<key column="RecipeID" />
<one-to-many class="OurRecipes.Domain.RecipeImage, OurRecipes.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
<many-to-one name="Category" column="CategoryID" />
<bag name="Comments" inverse="true" cascade="all">
<key column="RecipeID" />
<one-to-many class="OurRecipes.Domain.Comment, OurRecipes.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
<many-to-one name="User" column="EnteredByID" />
<bag name="Ingredients" inverse="true" cascade="all">
<key column="RecipeID" />
<one-to-many class="OurRecipes.Domain.Ingredient, OurRecipes.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</bag>
</class>

To build dynamic queries, I would use the criteria API. This makes the dynamic query much more stable, because you don't need string operations to build it up.
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r")
.CreateCriteria("Ingredients", "i", JoinType.InnerJoin)
.Add(
Expression.Disjunction() // OR
.Add(Expression.Like("i.IngredientName", "%pasta%"))
.Add(Expression.Like("i.IngredientName", "%wine%"))
.Add(Expression.Like("r.RecipeTitle", "%pasta%"))
.Add(Expression.Like("r.RecipeTitle", "%wine%")));
List<Recipe> result = query.List<Recipe>();
Edit:
For eager loading you could set the fetch-mode:
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r")
.SetFetchMode("Images", FetchMode.Join)
.SetFetchMode("Comments", FetchMode.Join)
.SetFetchMode("Ingredients", FetchMode.Join)
But I wouldn't do this because you get the results multiplied by the number of Images, Comments and Ingredients. So if you had 4 Images, 2 Comments and 12 Ingredients, you get your recipe 96 times. You don't recognize this, because NHibernate puts the things together again, but it generates traffic between the application and the database. So better let NHibernate load it with separate queries.
One more edit to show dynamic query composition.
// filter arguments, all are optional and should be omitted if null
List<string> keywords;
TimeSpan? minCookingTime;
TimeSpan? maxCookingTime;
int? minRating;
int? maxRating;
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r");
if (keyword != null)
{
// optional join
query.CreateCriteria("Ingredients", "i", JoinType.InnerJoin);
// add keyword search on ingredientName and RecipeTitle
var disjunction = Expression.Disjunction();
foreach (string keyword in keywords)
{
string pattern = String.Format("%{0}%", keyword);
disjunction
.Add(Expression.Like("i.IngredientName", pattern))
.Add(Expression.Like("r.RecipeTitle", pattern));
}
query.Add(disjunction)
}
if (minCookingTime != null)
{
query.Add(Expression.Ge(r.CookingTime, minCookingTime.Value));
}
if (maxCookingTime != null)
{
query.Add(Expression.Le(r.CookingTime, maxCookingTime.Value));
}
if (minRating != null)
{
query.Add(Expression.Ge(r.Rating, minRating.Value));
}
if (maxRating != null)
{
query.Add(Expression.Le(r.Rating, maxRating.Value));
}

Both Stefan's and Sathish's examples concatenate % operators into the SQL. This is unnecesary as Restrictions.Like (nhib 2.0+) and Expression.Like (before v2.0) have 3 parameter versions with a MatchMode.
Disjunction keywordsCriteria = Restrictions.Disjunction();
foreach (var keyword in keywords)
{
keywordsCriteria.Add(Restrictions.Like("i.IngredientName", keyword, MatchMode.Anywhere));
keywordsCriteria.Add(Restrictions.Like("r.RecipeTitle", keyword, MatchMode.Anywhere));
}
Full text queries are also available with NHibernate Search. See Ayende's example for more details.

Here is the above criteria with dynamic keywords
string searchQuery = "wine pasta";
ICriteria query = Session.CreateCriteria(typeof(Recipe), "r")
.CreateCriteria("Ingredients", "i", JoinType.InnerJoin)
.SetFetchMode("Images", FetchMode.Join)
.SetFetchMode("Comments", FetchMode.Join)
.SetFetchMode("Ingredients", FetchMode.Join)
.SetResultTransformer(new DistinctRootEntityResultTransformer());
var keywords = searchQuery.Split(' ');
Disjunction keywordsCriteria = Restrictions.Disjunction();
foreach (var keyword in keywords)
{
keywordsCriteria.Add(Restrictions.Like("i.IngredientName", string.Format("%{0}%", keyword)));
keywordsCriteria.Add(Restrictions.Like("r.RecipeTitle", string.Format("%{0}%", keyword)));
}
query.Add(keywordsCriteria);
List<Recipe> result = query.List<Recipe>();

Related

Hibernate: join with 2 NOT primary key

i want to do a left outer join from 2 table, but with 2 key that are not primary key.
This is the native sql query used:
Maybe there is a better way to do it?
SQLQuery query = session.createSQLQuery("
select {A.*},{B.*} from A_TABLE A left outer join B_TABLE B on A.QUOTE_ID = B.QUOTE_NUM ")
.addEntity("A", A_Class.class)
.addJoin("B", "A.bDocs");
for(Object result : query.list())
{
....
}
The mapping A file:
<class name="A_Class" table="A_TABLE" schema="S">
<id name="rowId" type="string">
<column name="ROW_ID" length="60" />
<generator class="assigned" />
</id>
<set name="BDocs" inverse="true" fetch="select" lazy="false">
<key>
<column name="QUOTE_NUM" length="60" not-null="true" />
</key>
<one-to-many class="B_Class" />
</set>
A_Class.java
public class A_Class implements java.io.Serializable
{
private String rowId;
private String QuoteId;
private Set BDocs= new HashSet(0);
// omitted all the set and get
}
The mapping B file:
<hibernate-mapping>
<class name="B" table="B_TABLE" schema="S">
<id name="rowId" type="string">
<column name="ROW_ID" length="60" />
<generator class="assigned" />
</id>
<many-to-one name="A" class="A_Class" fetch="select" lazy="false" outer-join="true" foreign-key="QuoteId" property-ref="QuoteId">
<column name="QUOTE_NUM" length="60" not-null="true" />
</many-to-one>
B_Class.java
public class B_Class implements java.io.Serializable
{
private String rowId;
private String quoteNum;
// omitted all the set and get
}
From this query i obtain 2 objects, one of A type and the other of B Type with a lot of correct datas but the set BDocs in the object of A type isn't filled. The goal is to get only the A Object with the variable BDocs filled with the B Objects.
I don't understand if the problem is in the query or in the mapping files. Anyone can help me?

HQL Query SELECT with NOT IN

I need a HQL Query but I just dont get it.
SELECT * FROM Poll WHERE pid NOT IN (
SELECT pid FROM votes WHERE uid = 'theuserid')
I want all List of PollĀ“s back where the uid not in the table votes exists.
Also helpfull would be the hql query where the uid in the table votes exists, but I guess this is very similar ;-)
These are the 2 classes:
public class Poll {
private int pid;
private String name;
private String description;
private Date deadline;
private Set<Team> teams = new HashSet<Team>(0);
//some getter & setter
}
public class Vote {
private int vid;
private String uid;
private int pid;
private int tid;
private int votes;
//some getter & setter
}
Can smbdy please help me. I guess it is a join with a WHERE and NOT LIKE but I just dont get it.
Merci!
This is btw the hibernate mapping:
<hibernate-mapping>
<class name="package.model.Poll" table="poll">
<id name="pid" column="pid" >
<generator class="increment"/>
</id>
<property name="name" column="name" />
<property name="description" column="description" />
<property name="deadline" type="timestamp" column="deadline" />
<set name="teams" table="pollchoice"
inverse="false" lazy="false" fetch="select" cascade="all" >
<key>
<column name="pid" not-null="true" />
</key>
<many-to-many entity-name="knt.exceedvote.model.Team">
<column name="tid" not-null="true" />
</many-to-many>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="package.model.Vote" table="votes">
<id name="vid" column="vid" >
<generator class="increment"/>
</id>
<property name="pid" column="pid" />
<property name="uid" column="uid" />
<property name="tid" column="tid" />
<property name="votes" column="votes" />
</class>
</hibernate-mapping>
Please keep in mind, Hibernate is designed using the notion of Object Graph, which is a name given to the relational objects.
That core concept is missing in your mapping (Poll and Vote seem to be isolated) hence I doubt you can use HQL in its current state.
In my opinion, you have two options:
Define the relationship between Poll, pid and Vote, uid. Then you should be able to write simple HQL.
Use native SQL through Hibernate session itself.

Cascade ="all" not saving child entities

I think the object model below is saying a Party and PartyName to have a many to one relatioship. An I think the cascade=all i the Party.hbm sshould be having NHib save the child PartyName(s).
But it clearly isn't...
Can someone explain why PartyName isn't being saved with Party and what to do to fix?
Cheers,
Berryl
MAPPING
<class name="Party" table="Parties">
<id name="Id">
<column name="PartyId" />
<generator class="hilo" />
</id>
<discriminator column="Type" not-null="true" type="string" />
<set access="field.camelcase-underscore" cascade="all" inverse="true" name="Names">
<key foreign-key="Party_PartyName_FK">
<column name="PartyNameId" />
</key>
<one-to-many class="Parties.Domain.Names.PartyName, Parties.Domain" />
</set>
<subclass
name="Smack.Core.TestingSupport.NHibernate.TestableDomain.SomeDopeyDomainModel.Student, Smack.Core.TestingSupport"
discriminator-value="Student"
>
<property name="Number" />
<many-to-one
class="Smack.Core.TestingSupport.NHibernate.TestableDomain.SomeDopeyDomainModel.Course, Smack.Core.TestingSupport"
foreign-key="Course_FK"
name="Course">
<column name="CourseId" index="CourseIndex" />
</many-to-one>
</subclass>
<many-to-one access="field.camelcase-underscore" class="Parties.Domain.Party" foreign-key="Party_FK" name="Party">
<column name="PartyId" index="PartyIndex" not-null="true"/>
</many-to-one>
<property name="TheRequiredName" not-null="true" length="50"/>
<property name="EverythingElse" />
<property name="ContextUsed" length="50"/>
<property name="Salutation" length="20"/>
<property name="EffectivePeriod" type="Smack.Core.Data.NHibernate.UserTypes.DateRangeUserType, Smack.Core.Data">
<column name="EffectiveStart"/>
<column name="EffectiveEnd"/>
</property>
Failing Test (and output)
[Test]
public void CanSaveAndLoad_AllProperties()
{
var partyName = NameSeeds.DevName;
partyName.Party = _party;
Assert.That(_party.Names.First(), Is.EqualTo(partyName));
using (var tx = _session.BeginTransaction())
{
_session.Save(_party);
tx.Commit();
}
_session.Clear();
Party foundParty;
using (var tx = _session.BeginTransaction())
{
foundParty = _session.Get<Party>(_party.Id); *** <=== name s/b saved!!
tx.Commit();
}
PartyName foundName = foundParty.Names.First();
//found.Look();
Assert.That(foundName, Is.EqualTo(partyName));
Assert.That(foundName.Party, Is.Not.Null);
Assert.That(foundName.TheRequiredName, Is.EqualTo(partyName.TheRequiredName));
Assert.That(foundName.EverythingElse, Is.EqualTo(partyName.EverythingElse));
Assert.That(foundName.ContextUsed, Is.EqualTo(partyName.ContextUsed));
Assert.That(foundName.Salutation, Is.EqualTo(partyName.Salutation));
Assert.That(foundName.EffectivePeriod, Is.EqualTo(partyName.EffectivePeriod));
}
NHibernate: INSERT INTO Parties (Type, PartyId) VALUES ('Parties.Domain.Party', #p0);#p0 = 32768 [Type: Int32 (0)]
NHibernate: SELECT party0_.PartyId as PartyId2_0_, party0_.Number as Number2_0_, party0_.CourseId as CourseId2_0_, party0_.Type as Type2_0_ FROM Parties party0_ WHERE party0_.PartyId=#p0;#p0 = 32768 [Type: Int32 (0)]
With the mapping of the Names <set> inverse=true, you will have to explicitly call session.Save(partyNameObject) on each member of the collection. If you are looking to have NHibernate automatically save the members of the set when the PartyObject is saved, you need to change the Names <set> inverse attribute to inverse=false. This tells Nhibernate that you want Party to control the relationship between Party and PartyName. You must also remember to add each partyNameObject to the Party.Names collection. Otherwise, they won't be saved when you call Session.Save(partyObject). Keep in mind that having the parent control the relationship may be handy, but if you happen to save the PartyObject without having Loaded the PartyNames collection, NHibernate will update their Party FK to Null. In this scenario with certain Cascade options set on the Names <set>, you might find Nhibernate Deleting them as well.

Nhibernate one-to-many with table per subclass

I am customizing N2CMS's database structure, and met with an issue. The two classes are listed below.
public class Customer : ContentItem
{
public IList<License> Licenses { get; set; }
}
public class License : ContentItem
{
public Customer Customer { get; set; }
}
The nhibernate mapping are as follows.
<class name="N2.ContentItem,N2" table="n2item">
<cache usage="read-write" />
<id name="ID" column="ID" type="Int32" unsaved-value="0" access="property">
<generator class="native" />
</id>
<discriminator column="Type" type="String" />
</class>
<subclass name="My.Customer,My" extends="N2.ContentItem,N2" discriminator-value="Customer">
<join table="Customer">
<key column="ItemID" />
<bag name="Licenses" generic="true" inverse="true">
<key column="CustomerID" />
<one-to-many class="My.License,My"/>
</bag>
</join>
</subclass>
<subclass name="My.License,My" extends="N2.ContentItem,N2" discriminator-value="License">
<join table="License" fetch="select">
<key column="ItemID" />
<many-to-one name="Customer" column="CustomerID" class="My.Customer,My" not-null="false" />
</join>
</subclass>
Then, when get an instance of Customer, the customer.Licenses is always empty, but actually there are licenses in the database for the customer. When I check the nhibernate log file, I find that the SQL query is like:
SELECT licenses0_.CustomerID as CustomerID1_,
licenses0_.ID as ID1_,
licenses0_.ID as ID2_0_,
licenses0_1_.CustomerID as CustomerID7_0_,
FROM n2item licenses0_
inner join License licenses0_1_
on licenses0_.ID = licenses0_1_.ItemID
WHERE licenses0_.CustomerID = 12 /* #p0 */
It seems that nhibernate believes that the CustomerID is in the 'n2item' table. I don't know why, but to make it work, I think the SQL should be something like this.
SELECT licenses0_.ID as ID1_,
licenses0_.ID as ID2_0_,
licenses0_1_.CustomerID as CustomerID7_0_,
FROM n2item licenses0_
inner join License licenses0_1_
on licenses0_.ID = licenses0_1_.ItemID
WHERE licenses0_1_.CustomerID = 12 /* #p0 */
Could any one point out what's wrong with my mappings? And how can I get the correct licenses of one customer? Thanks in advance.
I'm not sure whether the SQL is incorrect, because the parent class mapping uses a discriminator so I'd expect all properties to be stored in the same table as the base class (n2item). However I'm not familiar with the "join table" syntax, I generally use joined-subclass so I might be misunderstanding.
Assuming the subclass mapping is correct, could the problem with the licenses be something to do with no Cascade setting being set for that collection?

Nhibernate query issue

Considering the following mapping file where TemporaryAccessOpenCommand and TemporaryAccessCloseCommand both inherit from base class Command
<class name="Command" table="xxx_Commands" lazy="false">
<id name="Id" type="Int32" unsaved-value="0">
<generator class="identity"/>
</id>
<property name="BeginDate" />
<property name="EndDate" />
<component name="Result" class="CommandResult">
<property name="Status" column="ResultStatus"/>
<property name="Details" column="ResultDetails" />
</component>
<many-to-one name="Requestor" class="xxx.Domain.SessionInfo, xxx.Domain" column="SessionInfoId" lazy="false" />
<joined-subclass name="xxx.TemporaryAccess.Commands.TemporaryAccessCloseCommand, xxx.TemporaryAccess" table="xxx_Commands_TemporaryAccess_Close">
<key column="CommandId"/>
</joined-subclass>
<joined-subclass name="xxx.TemporaryAccess.Commands.TemporaryAccessOpenCommand, xxx.TemporaryAccess" table="xxx_Commands_TemporaryAccess_Open">
<key column="CommandId"/>
<many-to-one name="EndUser" ...... />
<property name="Reason"/>
<property name="AccessRight"/>
<property name="AccessType"/>
<bag name="CloseAccessCommands" cascade="all" lazy="false">
<key column="OpenCommandId"/>
<one-to-many class="xxx.TemporaryAccess.Commands.TemporaryAccessCloseCommand, xxx.TemporaryAccess"/>
</bag>
</joined-subclass>
What would be the nhibernate query to retrieve every OpenAccessCommand having no succeeded CloseAccessCommand ?
I tried this :
public IList<TemporaryAccessOpenCommand> FindOpenCommandsWithoutSucceededCloseCommand()
{
return this.HibernateTemplate.ExecuteFind<TemporaryAccessOpenCommand>(delegate(ISession session)
{
return session.CreateCriteria(typeof(TemporaryAccessOpenCommand))
.CreateCriteria("CloseAccessCommands")
.Add(Expression.Not(Expression.Eq("Result.Status", CommandStatus.Succeeded)))
.List<TemporaryAccessOpenCommand>();
});
}
But it will return an OpenAccessCommand that has tow CloseCommand (one failed and one succeeded) when it should return an empty list.
Thanks for your help (and excuse my poor english)
I managed to get something working this way (I'm open to suggestions if it's not the best way)
public IList<TemporaryAccessOpenCommand> FindOpenCommandsWithoutSucceededCloseCommand()
{
return this.HibernateTemplate.ExecuteFind<TemporaryAccessOpenCommand>(delegate(ISession session)
{
string sql = string.Format(#"
(
SELECT temp_c.OpenCommandId
FROM VSA2_Commands_TemporaryAccess_Close temp_c
INNER JOIN VSA2_Commands c
ON temp_c.CommandId = c.Id
WHERE c.ResultStatus = {0}
) AS OpenCommandId"
, (int)CommandStatus.Succeeded);
var subCriteria = DetachedCriteria.For<TemporaryAccessCloseCommand>();
subCriteria = subCriteria.SetProjection(Projections.SqlProjection(sql, new string[] { "OpenCommandID" }, new IType[] { NHibernateUtil.Int32 }));
return session.CreateCriteria(typeof(TemporaryAccessOpenCommand))
.Add(Expression.Eq("Result.Status", CommandStatus.Succeeded))
.Add(Subqueries.PropertyNotIn("Id", subCriteria))
.List<TemporaryAccessOpenCommand>();
});
}