NHibernate - need help with ICriteria query - nhibernate

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);

Related

NHibernate: Error when saving Bag with NOT-Nullable Constraint

Given are tables Item and ItemTranslation where the latter has a NOT-Nullable foreign key on Item.
ItemTranslation.hbm.xml only has its properties Text and LanguageCode, it does NOT map Item.
Item.hbm.xml:
<bag name="Translations" cascade="all-delete-orphan">
<key column="ItemID" />
<one-to-many class="ItemTranslation, SomeNamespace" />
</bag>
Now when I do the following:
Item item = new Item();
item.Translations.Add( new ItemTranslation { LanguageCode = "DE", Text = "Test DE" } );
item.Translations.Add( new ItemTranslation { LanguageCode = "EN", Text = "Test EN" } );
item.Save();
NHibernate throws the following exception:
System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'ItemID', table 'someDb.dbo.ItemTranslation'; column does not allow nulls. INSERT fails.
Cannot insert the value NULL into column 'ItemID', table 'someDb.dbo.ItemTranslation'; column does not allow nulls.
I could fix it either by mapping ItemID as many-to-one in IssuedItemTranslation.hbm.xml or making the column NULLable.
But both ways are kind of ugly.
Is there any other possibility? Maybe with some change in the bag-mapping?
Thank you in advance.
If we do not want to use inverse mapping (see below) .. we must left the column in DB nullable.
That is how it works. NHibernate will
insert children (or parent)
insert parent (or children) read more here 9.6. Flush
update children with parent id
Another way: Inverse mapping
There is nothing bad on explicit Item back reference mapping on ItemTranslatin. I do that always. And If we really do not like it.. it could be protected property.
But then, we would need inverse mapping.
<bag name="Translations" cascade="all-delete-orphan" inverse="true">
And also set the reference on both sides.
Item item = new Item();
var tr1 = new ItemTranslation {
LanguageCode = "DE",
Text = "Test DE"
Item = item } ;
item.Translations.Add(tr1);
...
This solution does not require item column to be nullable...
That would be the way I suggest. Read more here:
Minimal and correct way to map one-to-many with NHibernate
It works when mapping the bag as follows:
<bag name="Translations" cascade="all-delete-orphan">
<key column="ItemID" not-null="true" update="false" />
<one-to-many class="ItemTranslation, SomeNamespace" />
</bag>
Is this the correct solution?

Can you create a Restriction for a Detached Criteria on a primitive collection?

I would like to know if there is a way to create a Restriction on a primitive collection of a Model in NHibernate.3.3.3?
Here's the details:
class Parent {
IEnumerable<string> ChildNames { get; set; }
}
I need to search like so:
private DetachedCriteria BuildQuery() {
var inNames = { "Bob", "Sam", "Dan" };
var query = DetachedCriteria.For<Parent>("parent");
query.Add(Restrictions.In("ChildNames", inNames));
return query;
}
I found this old question that says it's not possible, but given the fact that it's old and doesn't have a ton of upvotes, I'd like to confirm before I refactor.
If I can do it and I'm totally botching it, I'll take that help as well!
In this scenario, we can use Projection (something less type-safe, then mapped Property, but more flexible).
Let's expect the mapping like this:
<bag name="ChildNames" inverse="false" lazy="true" table="[dbo].[ChildNames]"
cascade="all"
batch-size="25">
<key column="ParentId" />
<element type="System.String" column="ChildName" />
</bag>
Then we can adjust the Build query method like this:
protected virtual DetachedCriteria BuildQuery()
{
var inNames = new [] { "Bob", "Sam", "Dan" };
// parent query reference
var query = DetachedCriteria.For<Parent>("parent");
// reference to child query
var child = query.CreateCriteria("ChildNames");
// let's project the column name of the Element, e.g. 'Name'
var columnNameProjection = Projections.SqlProjection(
"ChildName as name", null, new IType[] { NHibernateUtil.String }
);
// in clause
child.Add(Restrictions.In(
columnNameProjection, inNames
));
return query;
}
And this is what we will get:
SELECT ...
FROM Parent this_
inner join [dbo].[ChildNames] childNames3_
on this_.ParentId=childNames3_.ParentId
WHERE ChildName in (#p0, #p1, #p2)
...
#p0=N'Bob',#p1=N'Sam',#p2=N'Dan'
The caveat:
While this is in deed working... the ChildName is used without the Alias. That could be pretty tricky to fulfill... so be careful, if there are more columns with the name ChildName in this scenario
I ended up, like many, refactoring the collection into a strong type.

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 HQL Query : How to set a Collection as a named parameter of a Query with composite key?

Given the following HQL Query:
from Foo foo where foo.id in (:fooIds)
but here i have composite key in the Id ex we have two PK1 and pk2 as Id's.
How can we implement this query..
how can i pass both paramets in setparameters function of query
My question is similar this question
HBM file containing composite key is present below
<?xml version="1.0"?>
<composite-id>
<key-property name="foo1" column="FOO1" type="java.lang.String" length="36"/>
<key-property name="foo2" column="FOO2" type="java.lang.Short" />
</composite-id>
<property name="EffectiveDt" type="java.sql.Date" column="EFFECTIVE_DT" />
<property name="effectiveTypeCd" type="java.lang.String" column="CERT_EFF_TYPE_CD" />
<property name="statusCd" type="java.lang.String" column="CERT_STATUS_CD" />
</class>
Are you using a composite id? Do you have a separate class representing the composite-id or do you have 2 fields in Foo and you want to search using them in your query?
Posting you Foo class would help!
I'm not 100% sure you can use in in this case. One thing you can do is to build the query manually with something like
String hqlQuery = "from Foo foo where "
boolean first = true;
for( ID id : fooids ) {
if( first ) {
hqlQuery += "foo.id = ?";
first = false;
} else {
hqlQuery += " OR foo.id = ?";
}
}
Query q = em.createQuery(hqlQuery);
int position = 0;
for( ID id : fooids ) {
q.setParameter(position, id);
position++;
}
You might want to double check the code, as I'm writing it here, so there's a big chance there's a typo or two.

nHibernate join query

i have two tables (graph representation - one table is nodes, other is link between nodes), and i want to write this query in nHibernate:
SELECT
distinct(t.id), t.NodeName, e.Fk_linkOne, e.Fk_linkTwo, e.RelationName
FROM Nodes t
INNER JOIN NodeRelation e ON t.Id=e.Fk_linkOne OR t.Id=e.Fk_linkTwo
where (e.Fk_linkOne =84 OR e.Fk_linkTwo=84 ) AND t.Id!=84
I did not find how to connect two tables wiht join, that have OR in it..
ICriteria criteriaSelect = Session
.CreateCriteria(typeof(NodeRelation ), "nodeRelations")
.CreateCriteria("nodeRelations.Node", "node",
NHibernate.SqlCommand.JoinType.InnerJoin)
You can only define your joins as per the associations you have defined in your mappings. As far as I know you can't define OR style relationships in Nhibernate. Consider using a self referential style graph representation.
public class Node
{
public IList<Node> Parents { get; set; }
public IList<Node> Children { get; set; }
}
<bag name="Parents" table="Node_Relation">
<key column="ChildId" />
<many-to-many class="Node" column="ParentId" />
</bag>
<bag name="Children" table="Node_Relation">
<key column="ParentId" />
<many-to-many class="Node" column="ChildId" />
</bag>
You should be using DetachedCriteria to get the same done. I am not sure about your query but I will jsut give a shot.
var dc1= DetachedCriteria.For(typeof( NodeRelation )).Add(Restrictions.Eq("Fk_linkOne", 84))
.SetProjection(Projections.Property("Fk_linkOne"));
var dc2= DetachedCriteria.For(typeof( NodeRelation )).Add(Restrictions.Eq("Fk_linkTwo", 84))
.SetProjection(Projections.Property("Fk_linkTwo"));
Session.CreateCriteria(typeof(Nodes))
.Add(Subqueries.PropertyIn("Id", dc1))
.Add(Subqueries.PropertyIn("Id", dc2))
.Add(Restrictions.Eq("Id", 84)).List<Nodes>;
Hope the above query is corrrect. please let me know if you cant get it to work after obs trying smethings and let me know wat u tried.
It always depends on your classes and not so much on your tables. Remember, you are using a ORM and you are working with a class model.
Assumed that your classes look like this:
class Node
{
List<Node> Relations { get; private set; }
List<Node> InverseRelations { get; private set; }
}
You may map it like this:
<class name="Node">
<!-- .... -->
<bag name="Relations" table="NodeRelation">
<key name="Fk_linkOne">
<many-to-many class="Node" column="Fk_linkTwo"/>
</bag>
<bag name="InverseRelations" table="NodeRelation" inverse="true">
<key name="Fk_linkTwo">
<many-to-many class="Node" column="Fk_linkOne"/>
</bag>
</class>
This way you get asymetrical relations (this means: when Node A relates to Node B, B isn't necessarily related to A, except of the InverseRelation of course). I don't know what you actually need, so this is an assumption based on your database design.
Your query may look like this:
from Node n
where
:x in elements(n.Relations)
or :x in elements(n.InverseRelations)
Note: x is an entity type, not just an id (you need to load it using session.Load<Node>(84)),
Another way for the samy query:
select distinct n
from Node n
inner join n.Relations e1
inner join n.InverseRelations e2
where e1.id = 84 or e2.id = 84
Or another way without the use of the inverse relations:
select n
from Node n, Node n2 inner join n.Relations e
where
(n = n2 and e.id = 84)
OR (n = e and n2.id = 84)
In criteria I would take the second solution and write it like this:
session.CreateCriteria<Node>("n")
.SetProjection(Projections.Distinct("n"))
.CreateCriteria("Relations", "e1")
.CreateCriteria("InverseRelations", "e2")
.Add(Expression.Or(
Expression.Eq("e1.id", 84),
Expression.Eq("e2.id", 84));