nHibernate join query - nhibernate

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

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?

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.

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

NHibernate filter collection

Using NHibernate I want to filter a collection in a class to contain ONLY a subset of possible objects. Below I am including a sample table data to help explain. I can find no way to do this using NHibernate.
Table:DataObject
DataObjectId(PK) / Name / CurrentVersion
11 "data.txt" 2
12 "info.txt" 3
Table:DataObjectVersion
Id / Comment / VersionNumber / DataObjectId(FK)
31 "Genesis" 1 11 <= Ignore this object
32 "Changed data" 2 11 <= Get this object
34 "Genesis" 1 12 <= Ignore this object
35 "Changed info" 2 12 <= Ignore this object
36 "Added info" 3 12 <= Get this object
I want to join on a non-foreign key DataObject.CurrentVersion = DataObjectVersion.VersionNumber for each DataObject in one command.
Here are the classes and mapping files:
public class DataObject
{
public virtual int DataObjectId { get; set; }
public virtual string Name { get; set; }
public virtual int CurrentVersionNumber { get; set; }
public virtual IList<DataObjectVersion> Versions { get; set; }
}
<class name="DataObject" table="DataObject" lazy="false">
<id name="DataObjectId" column="DataObjectId" type="int">
<generator class="assigned" />
</id>
<property name="Name" column="Name" type="String(512)" />
<property name="CurrentVersionNumber" column="CurrentVersionNumber" type="int" />
<bag name="Versions" cascade="all-delete-orphan" inverse="true" lazy="false" >
<key column="DataObjectId" />
<one-to-many class="DataObjectVersion" />
</bag>
</class>
public class DataObjectVersion
{
public virtual int DataObjectVersionId { get; set; }
public virtual string Comment { get; set; }
public virtual int VersionNumber { get; set; }
public virtual int DataObjectId { get; set; }
}
<class name="DataObjectVersion" table="DataObjectVersion" lazy="false">
<id name="Id" column="DataObjectVersionId" type="int">
<generator class="assigned" />
</id>
<property name="Comment" column="Comment" type="String(512)" />
<property name="VersionNumber" column="VersionNumber" type="int" />
<property name="DataObjectId" column="DataObjectId" type="int" />
</class>
if you want to filter the collection on demand, using a filter is a valid choice.
You would need to declare the filter on both the Version class and in the bag element and apply the filter from the NHibernateSession.EnableFilter method
if you always want to fetch a single Version in the bag then implement a 'where' in the mapping of the bag:
<bag name="Versions" cascade="all-delete-orphan" inverse="true" lazy="false" where="CurrentVersionNumber = Versions.VersionNumber" >
<key column="DataObjectId" />
<one-to-many class="DataObjectVersion" />
</bag>
note that in the 'where' you write proper SQL not HQL and as such the proper SQL i write above probably has to be changed to reflect your schema
Additionally if a single object is to be fetched setting up a bag and the according IList may be an overkill.
Applying a formula property and a DataObjectVersion object in the class may be more appropriate
in the class DataObject replace the IList with
public virtual DataObjectVersion Version { get; set; }
and in the mapping replace the 'bag' with something in the lines of
<property name="Version" type="DataObjectVersion" update="false" insert="false" formula="(select v.DataObjectVersionId, v.Comments, v.VersionNumber, v.DataObjectId from DataObjectVersion v where v.VersionNumber = CurrentVersionNumber)" />
again only proper SQL is allowed
i've used computed properties with native datatypes (datetime, string etc) and fetching an Entity may (or may not) need something more or different
Last but not least, you could apply a filter on the collection after you have fetched the primary object DataObject by creating a filter on the collection
IList<DataObjectVersion> fVersion =
NHibernateSession.CreateFilter(do.Versions, "where VersionNumber = :ver")
.SetParameter("ver", do.CurrentVersionNumber)
.List<DataObjectVersion>();
where the do.Versions collection is not initialized, only the results fetched in the separate fVersion collection and this is a second SELECT after already having made the round-trip to the db for the DataObject fetch.
Presumably your VersionNumber increments as the user changes the data and you're trying to get the latest one. If you consider the VersionNumber as an "Age" field instead (i.e. where 0 is the latest / youngest version, 1 is the next oldest and so on) then your problems becomes how to get all the entities with an Age of 0. This can be done using a filter: http://nhibernate.info/doc/nh/en/index.html#objectstate-filters