nHibernate and concurrency check - nhibernate

I want to achieve concurrency check using nHibernate 3 using UnitOfWork pattern.
To be more precise:
open new session session,
load entity in a session,
close session,
give user some time to edit data in loaded entity,
open new session,
update data
close session.
I'm using timestap to version entity.
Here is my mapping file
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="...."
namespace="...."
default-lazy="false">
<class name="Employee"
optimistic-lock="version"
dynamic-update="true">
<id name="Id">
<generator class="native" />
</id>
<version column="LastEditDate" generated="always" type="timestamp" />
<property name="Name" not-null="1" length="255" />
<property name="LastEditUser" not-null="1" length="255"/>
</class>
</hibernate-mapping>
I have no idea how to update entity in session context
var entity = <updated by user>
using (var session = GetNewSession())
{
//todo: load current version value / attach entity to context
session.SaveOrUpdate(entity);
//if concurency check fails, StaleObjectException (or similar) is expected to be thrown
}
In SQL it should work like this
UPDATE ENTITY SET LastEditDate = #P1, ... WHERE ID = #P2 AND LastEditDate = #P3
where:
#P1 - new LastEditDate
#P2 - entity ID
#P3 - previous LastEditDate
If ROWSMODIFIED = 1 then update was successfull, else if = 0 then ConcurrencyException
Using Linq2Sql it was very simple: create versioning column, attach entity to new session context and try to update.
How can I do it in nHiberate? Is it supported?

session.Update(entity)
should be enough.

I think you should use session.Lock(entity,LockMode.Update).

Related

How to map an NHibernate entity to a query

I've seen a number of examples of this, and as far as I can tell my HBM file follows the same pattern, but it's not working. First, the file:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping default-cascade="save-update" xmlns="urn:nhibernate-mapping-2.2">
<class name="ThinAir" mutable="false" lazy="true" >
<id name="JobId">
<generator class="native" />
</id>
<property name="UserLogin"/>
<property name="UserEmail"/>
<property name="DateProcessed"/>
<loader query-ref="myquery"/>
</class>
<sql-query name="myquery">
<return class="ThinAir">
<return-property name="JobID" column="JobId"/>
<return-property name="userLogin" column="UserLogin"/>
<return-property name="DateProcessed" column="DateProcessed"/>
<return-property name="userEmail" column="UserEmail"/>
</return>
<![CDATA[
SELECT DISTINCT JobID,
userLogin,
DateProcessed,
useremail
FROM dbo.someothertable
]]>
</sql-query>
</hibernate-mapping>
"myquery" in-and-of-itself, works. That is to say, if I call
var x = session.GetNamedQuery("myquery").List();
I get a correct List of ThinAir objects.
But, when I try to get a list of ThinAirs like this:
var submissions = session.CreateCriteria<ThinAir>().List<ThinAir>();
I get
Test method testThinAir threw exception:
NHibernate.Exceptions.GenericADOException: could not execute query
[ SELECT this_.JobId as JobId16_0_, this_.UserLogin as UserLogin16_0_, this_.UserEmail as UserEmail16_0_, this_.DateProcessed as DateProc4_16_0_ FROM ThinAir this_ ]
My interpretation of this phenomenon is that NH is ignoring my <loader> tag and so trying to load the data from the underlying table, which by default it assumes to be named ThinAir because that's the name of the entity class, only there isn't any ThinAir table, hence the error message.
Is that interpretation correct? And in any case, what am I doing wrong and how can I do it right?
Thanks in advance.
Michael
One way how to achieve this, would be to move the mapping from a query into the subselect:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping default-cascade="save-update" xmlns="urn:nhibernate-mapping-2.2">
<class name="ThinAir" mutable="false" lazy="true" >
<subselect>
<![CDATA[
SELECT DISTINCT JobID,
userLogin,
DateProcessed,
useremail
FROM dbo.someothertable
]]>
</subselect>
... // rest of the mapping

Another NHibernate Mapping Mystery! Getting count in hbm file

I am trying to retrieve the count of items allocated to a container in my hbm file. I've done a bit of digging and managed to get my hbm code this far (below!). I want the count to be retrieved every time a container object is queried. I could use an interceptor but I assume there's a better way. Am I on the right track or should I use a different strategy to get the count loaded up?
Thanks.
P.S. We're using NH v2.2
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false" assembly="MyEntities" namespace="Entities.Containers"> <class name="Container" table="[Container]" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="Id" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="Capacity" column="Capacity">
<column name="Capacity" />
</property>
<property name="Description" column="Description" length="50" type="String">
<column name="Description" />
</property>
<loader query-ref="sqCurrentContainerAllocation"/> </class>
<sql-query name="sqCurrentContainerAllocation">
<return-scalar column="AllocatedItemsCount" type="int"></return-scalar>
SELECT COUNT(*) FROM [ContainerTracking]
WHERE [ContainerId] = :Id </sql-query>
</hibernate-mapping>
If you need to get some calculated property, you can use mapping with formula.
Let's extend your C# class:
public class Container
{
... // ID, Capacity, Description
public virtual int MyCount { get; set; }
And extend your mapping
<class name="Container" table="[Container]"
...
<property name="MyCount" insert="false" update="false" >
<formula>
(
SELECT count(*)
FROM [ContainerTracking] as ct
WHERE ct.[ContainerId] = Id
)
</formula>
</property>
the Id will be replaced with somethink like '_this.Id', the name of the column Id and its alias
This will of course load the count all the time (except projections) so think twice before use it

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.

Select unsaved object out of the session

Using NHibernate 2.1 on mssql with C#.
I have 2 diffrent classes witch use an "group" object.
<class name="OrderCode" table="OrderCode" polymorphism="explicit">
<id name="Id" column="ID">
<generator class="native" />
</id>
...
<many-to-one class="Group" name="Group" column="GRUPPE" cascade="all" />
...
and
<class name="Alotment" table="Alotment">
<id name="Id" column="ID">
<generator class="native" />
</id>
...
<many-to-one class="Group" name="Group" column="GRUPPE" cascade="all" />
...
An import script now creates first the OrderCode and select the group, if the group not exist it makes an new one. Later in the same script I create the alotment. Same way, select if there is the right group, if not the script create a group.
Sometimes OrderCode and Alotment need the same group, but if the group dosen't exist, the script creates two groups cause the secend select don't get the group out off the session.
I know its the same session. Is there a way to let IQuery, ICriteria or Session.Linq select not commit groups out of the session?
As far as I know if you save or update an entity using a session you should flush or commit your changes before getting it back from the same session.
Can't you use group object reference like this?
using(var session=SessionFactory.OpenSession())
{
var group=(Group)session.CreateQuery(group_query_string).UniqueResult;
if(group==null)
{
group=new Group();
session.SaveOrUpdate(group);
}
...
var orderCode=new OrderCode{Group=group};
session.SaveOrUpdate(orderCode);
...
// and later in your code
var alotment=new Alotment{Group=group};
session.SaveOrUpdate(alotment);
....
session.Flush();
}

NHibernate Criteria Type Mismatch

This code
ICriteria crit = service.Session.CreateCriteria(typeof (DatabaseVersion));
crit.Add(Restrictions.Eq("Id.Minor", 4));
IList<DatabaseVersion> list = crit.List<DatabaseVersion>();
causes the following error :NHibernate.QueryException: Type mismatch in NHibernate.Criterion.SimpleExpression: Id.Minor expected type System.Int16, actual type System.Int32
How can i correct this error? I set the type in the mapping file with no luck. I would prefer not having to type cast in code.
Added mapping file as requested.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Core.NUnit.Domain.DatabaseVersion,Core.NUnit" table="`DatabaseVersion`" lazy="true" >
<composite-id name="Id" class="Core.NUnit.Domain.DatabaseVersionId,Core.NUnit">
<key-property name="Major" column="`Major`" />
<key-property name="Minor" column="`Minor`" type="Int16"/>
<key-property name="Build" column="`Build`" />
<key-property name="Revision" column="`Revision`" />
</composite-id>
<property name="Description" column="`Description`"/>
<property name="DateApplied" column="`DateApplied`"/>
</class>
</hibernate-mapping>
I think the problem might be that C# cannot implicitly cast an int to a short. It looks like your mapping maps this field to an Int16 (short). The "4" you are passing in is allocated as an int by default. (I don't know why it would have trouble comparing an int to a short though...)
I don't think there is a literal suffix for short. You might be able to do this:
crit.Add(Restrictions.Eq("Id.Minor", (short)4));