where clause as a parameter - nhibernate

IN converting over a legacy application we need to convert named query to nhibernate. The problem is that the where clause is being set.
here is the mapping
<resultset name="PersonSet">
<return alias="person" class="Person">
<return-property column="id" name="Id" />
<return-property column="ssn" name="Ssn" />
<return-property column="last_name" name="LastName" />
<return-property column="first_name" name="FirstName"/>
<return-property column="middle_name" name="MiddleName" />
</return>
</returnset>
<sql-query name="PersonQuery" resultset-ref="PersonSet" read-only="true" >
<![CDATA[
SELECT
person.ID as {person.Id},
person.SSN as {person.Ssn},
person.LAST_NAME as {person.LastName},
person.MIDDLE_NAME as {person.MiddleName},
person.FIRST_NAME as {person.FirstName},
FROM PERSONS as person
where :value
]]>
</sql-query>
and the c# code:
String query = "person.LAST_NAME = 'Johnson'";
HibernateTemplate.FindByNamedQueryAndNamedParam("PersonQuery", "value", query);
The error:
where ?]; ErrorCode []; An expression of non-boolean type specified in a context where a condition is expected, near '#p0'.

This doesn't work because you try to replace :value with "person.LAST_NAME = 'Johnson'" wanting that the query becomes
SELECT person.ID, person.SSN, person.LAST_NAME, person.MIDDLE_NAME, person.FIRST_NAME
FROM PERSONS as person
where person.LAST_NAME = 'Johnson'
This won't work. You can only replace the 'Johnson' part dynamically not the whole condition. Thus what really gets generated is
SELECT person.ID, person.SSN, person.LAST_NAME, person.MIDDLE_NAME, person.FIRST_NAME
FROM PERSONS as person
where 'person.LAST_NAME = \'Johnson\''
Which obviously isn't a valid condition for the WHERE-part as there is only a literal but no column and operator to compare the field to.
If you only have to match against person.LAST_NAME rewrite the xml-sql-query to
<sql-query name="PersonQuery" resultset-ref="PersonSet" read-only="true" >
<![CDATA[
SELECT
...
FROM PERSONS as person
where person.LAST_NAME = :value
]]>
</sql-query>
And in the C# code set
String query = "Johnson";
If you need to dynamically filter by different columns or even multiple columns at a time use filters. e.g. like this (i made a few assumptions on you hibernate-mapping file)
<hibernate-mapping>
...
<class name="Person">
<id name="id" type="int">
<generator class="increment"/>
</id>
...
<filter name="ssnFilter" condition="ssn = :ssnValue"/>
<filter name="lastNameFilter" condition="lastName = :lastNameValue"/>
<filter name="firstNameFilter" condition="firstName = :firstNameValue"/>
<filter name="middleNameFilter" condition="middleName = :middleNameValue"/>
</class>
...
<sql-query name="PersonQuery" resultset-ref="PersonSet" read-only="true" >
...
FROM PERSONS as person
]]>
</sql-query>
<!-- note the missing WHERE clause in the PersonQuery -->
...
<filter-def name="ssnFilter">
<filter-param name="ssnValue" type="int"/>
</filter-def>
<filter-def name="lastNameFilter">
<filter-param name="lastNameValue" type="string"/>
</filter-def>
<filter-def name="middleNameFilter">
<filter-param name="midlleNameValue" type="string"/>
</filter-def>
<filter-def name="firstNameFilter">
<filter-param name="firstNameValue" type="string"/>
</filter-def>
</hibernate-mapping>
Now in your code you should be able to do
String lastName = "Johnson";
String firstName = "Joe";
//give me all persons first
HibernateTemplate.FindByNamedQuery("PersonQuery");
//just give me persons WHERE FIRST_NAME = "Joe" AND LAST_NAME = "Johnson"
Filter filter = HibernateTemplate.enableFilter("firstNameFilter");
filter.setParameter("firstNameValue", firstName);
filter = HibernateTemplate.enableFilter("lastNameFilter");
filter.setParameter("lastNameValue", lastName);
HibernateTemplate.FindByNamedQuery("PersonQuery");
//oh wait. Now I just want all Johnsons
HibernateTemplate.disableFilter("firstNameFilter");
HibernateTemplate.FindByNamedQuery("PersonQuery");
//now again give me all persons
HibernateTemplate.disableFilter("lastNameFilter");
HibernateTemplate.FindByNamedQuery("PersonQuery");
If you need still more dynamic queries (e.g. even changing the operator (=, !=, like, >, <, ...) or you have to combine restrictions logically (where lastname = "foo" OR firstname" = "foobar") then it's definitly time to look into the
Hibernate Criteria API

I'm unfamiliar with this HibernateTemplate syntax, but it looks like you're querying the original fieldname in SQL rather than the alias. Try this:
String query = "person.LastName = 'Johnson'";
or, maybe:
String query = "[person.LastName] = 'Johnson'";
or, possibly:
String query = "{person.LastName} = 'Johnson'";
Depends what sort of pre-processing is going on before the final SQL query is sent to the server.

That's because :value is a bind variable in the query; you cannot simply replace it with a string that contains an arbitrary string (that would become part of the query), only with an actual value. In your case, the value is "person.LAST_NAME = 'Johnson'", which is actually a string, not a boolean value. Boolean values would be true or false, both of which are rather useless for what you try to archive.
Bind variables more-or-less replace literals, not complex expressions.

Related

coldfusion ORM: many-to-many conditional property

I am trying to add a where clause to a many-to-many property I have defined in one of my objects. I must be doing something wrong though because I keep getting hibernate errors saying that the column doesn't exist.
in the template cfc:
<cfproperty
name="Settings"
fieldtype="many-to-many"
cfc="Setting"
linktable="settings_templates"
fkcolumn="templateID"
inversejoincolumn="settingsId"
where="deleted='false'"
>
In the settings cfc:
<cfproperty
name="templates"
fieldtype="many-to-many"
cfc="Template"
linktable="settings_templates"
fkcolumn="settingsId"
inversejoincolumn="templateID"
where="deleted='false'"
>
The error I am getting is:
08/02 16:06:27 [jrpp-170] HIBERNATE ERROR - [Macromedia][SQLServer
JDBC Driver][SQLServer]Invalid column name 'deleted'.
Can anyone see what I am doing wrong? there is a deleted column in both tables, but not in the link table.
The where property behavior for many-to-many is very strange...
In order to debug this, activate the Hibernate logging is primordial.
Refer you to this post: http://www.rupeshk.org/blog/index.php/2009/07/coldfusion-orm-how-to-log-sql/
Take this example:
Article.cfc
/**
* #output false
* #persistent true
* #table article
*/
component {
property name="id" fieldtype="id";
property name="title";
property
name="tags" singularname="tag"
fieldtype="many-to-many" cfc="Tag" linktable="link_article_tag" fkcolumn="articleId"
inversejoincolumn="tagId" where=" deleted = 0 "
;
}
Tag.cfc
/**
* #output false
* #persistent true
* #table tag
*/
component {
property name="id" fieldtype="id";
property name="name";
property name="deleted" dbdefault="0";
property
name="articles" singularname="article"
fieldtype="many-to-many" cfc="Article" linktable="link_article_tag" fkcolumn="tagId"
inversejoincolumn="articleId"
;
}
When I try to list all articles like this ormExecuteQuery('from Article'), see what is appened in the hibernate logs:
select
tags0_.articleId as articleId6_1_,
tags0_.tagId as tagId1_,
tag1_.id as id0_0_,
tag1_.name as name0_0_,
tag1_.deleted as deleted0_0_
from
link_article_tag tags0_
inner join
tag tag1_
on tags0_.tagId=tag1_.id
where
tags0_.deleted = 0
Damned! WTF :| tags0_ == join table ... You see what's wrong?
In order to understand this behavior, I'm going to activate HBMXML generation in Application.cfc with this.ormSettings.saveMapping = true
Here is the files:
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class entity-name="Tag" lazy="true"
name="cfc:www.app.models.test.Tag" table="tag">
<id name="id" type="string">
<column length="255" name="id"/>
</id>
<property name="name" type="string">
<column name="name"/>
</property>
<property name="deleted" type="string">
<column default="0" name="deleted"/>
</property>
<bag name="articles" table="link_article_tag">
<key column="tagId"/>
<many-to-many class="cfc:www.app.models.test.Article" column="articleId"/>
</bag>
</class>
</hibernate-mapping>
What we can see: The where attribute is set at the bag level, not at the to many-to-many. If I edit like this:
<bag name="tags" table="link_article_tag">
<key column="articleId"/>
<many-to-many class="cfc:www.app.models.test.Tag" column="tagId" where=" deleted = 0 "/>
</bag>
The generated SQL become:
select
tags0_.articleId as articleId62_1_,
tags0_.tagId as tagId1_,
tag1_.id as id59_0_,
tag1_.name as name59_0_,
tag1_.deleted as deleted59_0_
from
link_article_tag tags0_
inner join
tag tag1_
on tags0_.tagId=tag1_.id
where
tag1_.deleted = 0
This method works but is not good for code maintenance and readability.
If anyone has a better solution, I'm interested.
So far, the only way around this that I have found is to override the getter using ORMExecuteQuery(). There I can query the objects directly and look for things such as this. Personally, I prefer to work in cfscript, so my code would look something like this:
public array function getSettings() {
return ORMExecuteQuery("SELECT s FROM template t JOIN t.settings s WHERE t.id=:id AND s.deleted=:deleted", {
deleted=false, id=this.getId()
});
}
N.B. – EXAMPLE NOT TESTED
Of course, you don't have to use the parameters as I have, you could just use deleted=false, but that's up to you (I like using parameters, personally).
Also, don't forget the singularName attribute on many-to-many properties.
On a related note, I've had issues using properties on both objects. You really only need to use them on the template object, in this case. Think about it this way: A template needs to know its settings, but the settings don't need to know about the template they belong to – using properties on both objects in this way can lead to errors, in some cases.

Hibernate can't load Custom SQL collection

There is a table Item like,
code,name
01,parent1
02,parent2
0101,child11
0102,child12
0201,child21
0202,child22
Create a java object and hbm xml to map the table.The Item.parent is a Item whose code is equal to the first two characters of its code :
class Item{
String code;
String name;
Item parent;
List<Item> children;
.... setter/getter....
}
<hibernate-mapping>
<class name="Item" table="Item">
<id name="code" length="4" type="string">
<generator class="assigned" />
</id>
<property name="name" column="name" length="50" not-null="true" />
<many-to-one name="parent" class="Item" not-found="ignore">
<formula>
<![CDATA[
(select i.code,r.name from Item i where (case length(code) when 4 then i.code=SUBSTRING(code,1,2) else false end))
]]>
</formula>
</many-to-one>
<bag name="children"></bag>
</class>
</hibernate-mapping>
I try to use formula to define the many-to-one relationship,but it doesn't work!Is there something wrong?Or is there other method?
Thanks!
ps,I use mysql database.
add 2010/05/23
Pascal's answer is right,but the "false" value must be replaced with other expression,like "1=2".Because the "false" value would be considered to be a column of the table.
select i.code
from Item i
where (
case length(code)
when 4 then i.code=SUBSTRING(code,1,2)
else 1=2
end)
And I have another question about the children "bag" mapping.There isn't formula configuration option for "bag",but we can use "loader" to load a sql-query.I configure the "bag" as following.But it get a list whose size is 0.What's wrong with it?
<class>
... ...
<bag name="children">
<key />
<one-to-many class="Item"></one-to-many>
<loader query-ref="getChildren"></loader>
</bag>
</class>
<sql-query name="getChildren">
<load-collection alias="r" role="Item.children" />
<![CDATA[(select {r.*} from Item r join Item o where
o.code=:code and
(
case length(o.code)
when 2 then (length(r.code)=4 and SUBSTRING(r.code,1,2)=o.code)
else 1=2
end ))]]>
</sql-query>
According to the documentation, you're supposed to return the value of the computed foreign key, nothing more:
formula (optional): an SQL expression that defines the value for a computed foreign key.
So I would expect a query like this:
select i.code
from Item i
where (
case length(code)
when 4 then i.code=SUBSTRING(code,1,2)
else false
end)
Disclaimer: not tested.

NHibernate - need help with ICriteria query

I am having trouble getting my query by criteria to work.
I want to filter the UserPublications collection by userId but it is not filtering. The ClientPublications collection has filtered correctly though.
Any advice?
Thanks in advance.
public IList<ClientReport> GetAvailableClientReports(int userId)
{
ICriteria criteria = NHibernateSession.CreateCriteria(typeof(ClientReport))
.CreateCriteria("ClientPublications")
.Add(Expression.Eq("IsDownloaded", true))
.SetResultTransformer(CriteriaUtil.DistinctRootEntity)
.AddOrder(Order.Asc("Name"))
.CreateCriteria("UserPublications")
.CreateAlias("ClientUser", "user")
.Add(Expression.Eq("user.UserId", userId));
return GetByCriteria(criteria);
}
If you mapped the UserId property as "Id" in your mapping file (which you probably do if you used the same conventions as in this question), it should be:
.Add(Expression.Eq("user.Id", userId))
The Id property is a special case in NHibernate
why don't you create an alias for UserPublications and add the expression there? like
.CreateCriteria("UserPublications", "up")
.Add(Expression.Eq("up.ClientUser.UserId", userId));
or maybe
.CreateCriteria("UserPublications", "up")
.CreateAlias("up.ClientUser", "user")
.Add(Expression.Eq("user.UserId", userId));
as far as i can see calling
.CreateAlias("ClientUser", "user")
depends on NH's ability to detect where ClientUser exists and create the join which may not be working (bug or otherwise)
For future ref, I got it working by adding a filter in the mapping file
First define the filter in the parent class mapping:
<filter-def name="userFilter">
<filter-param name="userId" type="System.Int32"/>
</filter-def>
Then define filter further in the mapping to the collection
<bag name="UserPublications" access="property" lazy="true" cascade="all-delete-orphan">
<key column="ClientPublicationID"/>
<one-to-many class="ReportMgr.Model.ClientUserPublication, ReportMgr.Model" />
<filter name="userFilter" condition="ClientUserID = :userId"></filter>
</bag>
Then enable the filter and specify the parameter value just before executing the ICriteria query:
NHibernateSession.EnableFilter("userFilter").SetParameter("userId", userId);
ICriteria criteria = NHibernateSession.CreateCriteria(typeof(ClientReport))
.CreateCriteria("ClientPublications")
blah blah blah
return GetByCriteria(criteria);

Querying with 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>();

Hibernate - New colums from a joined table [duplicate]

This question already has answers here:
Hibernate One-To-Many Unidirectional on an existing DB
(4 answers)
Closed 2 years ago.
I have a class User object. I am trying to load this object from the Database using Hibernate.
My SQL statement is:
SELECT
users.uid AS uid,
users.deleted AS deleted,
users.username AS username,
users.passwd AS passwd,
users.disabled AS disabled,
users.lockout AS lockout,
users.expires AS expires,
data_firstname.value AS firstname,
data_lastname.value AS lastname
FROM
ac_users as users
LEFT JOIN
ac_userdef_data as data_firstname
ON
users.uid = data_firstname.parentuid AND
data_firstname.fieldname like 'firstname'
LEFT JOIN
ac_userdef_data as data_lastname
ON
users.uid = data_lastname.parentuid AND
data_lastname.fieldname like 'lastname'
WHERE
users.deleted = 0
My mapping for the User class is:
<class name="com.agetor.commons.security.User" table="ac_users">
<id name="uid" column="uid" type="long" >
<generator class="native"/>
</id>
<property name="deleted" column="deleted" />
<property name="username" column="username" />
<property name="password" column="passwd" />
<property name="firstname" column="firstname" />
<property name="lastname" column="lastname" />
<property name="disabled" column="disabled" />
<property name="lockout" column="lockout" />
<property name="expires" column="expires" />
</class>
My problem is that the table ac_users does not have a column 'firstname' or 'lastname'
These columns, only exist in my resultset from the SQL-join statement.
They do also not exist in the ac_userdef_data table. There i have 2 colums: fieldname and value. and 2 rows:
fieldname = 'firstname' with some value in the value column
and another row with
fieldname = 'lastname' with some value in the value column
How do i change my mapping file, so that Hibernate will understand that it needs to load the firstname and lastname column into my firstname and lastname fields on my POJO, while those columns dont actually exist on the referenced ac_users table.?
I now have some working code. The trick was to specify a loading Query for the User class. Hibernate then validates against the return from the Query instead of the Table design.
Class mapping
<class name="com.agetor.commons.security.User" table="ac_users">
<id name="uid" column="uid" type="long" >
<generator class="native"/>
</id>
<property name="deleted" column="deleted" />
<property name="username" column="username" />
<property name="password" column="passwd" />
<property name="firstname" column="firstname" />
<property name="lastname" column="lastname" />
<property name="disabled" column="disabled" />
<property name="lockout" column="lockout" />
<property name="expires" column="expires" />
<loader query-ref="GetAllUsers" />
</class>
My Query declaration
<sql-query name="GetAllUsers">
<return alias="user" class="com.agetor.commons.security.User" />
<![CDATA[
SELECT
users.uid AS uid,
users.deleted AS deleted,
users.username AS username,
users.passwd AS passwd,
users.disabled AS disabled,
users.lockout AS lockout,
users.expires AS expires,
data_firstname.value AS firstname,
data_lastname.value AS lastname
FROM
ac_users as users
LEFT JOIN
ac_userdef_data as data_firstname
ON
users.uid = data_firstname.parentuid AND
data_firstname.fieldname like 'firstname'
LEFT JOIN
ac_userdef_data as data_lastname
ON
users.uid = data_lastname.parentuid AND
data_lastname.fieldname like 'lastname'
WHERE
users.deleted = 0
]]>
</sql-query>
I had to use the line <return alias="user" class="com.agetor.commons.security.User" /> so that the returned collection was typed correctly