How to work with NHibernate and Oracle using Oracle Sequence?
When I call to the SaveOrUpdate() method, it only performs a select query against the Oracle sequence.
I have used an interceptor to look up the query the session was performing, and the is the instruction I get:
select INFO_ACCESS_REQS_ID_SEQ.nextval from dual;
And that is the only thing being executed against the underlying Oracle database when I call to ISession.SaveOrUpdate().
hibernate.cfg.xml
<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
<session-factory name="MyProject">
<property name="connection.driver_class">
NHibernate.Driver.OracleClientDriver
</property>
<property name="format_sql">true</property>
<property name="show_sql">true</property>
<property name="dialect">NHibernate.Dialect.Oracle10gDialect</property>
<property name="query.substitutions">
true 1, false 0, yes 'Y', no 'N'
</property>
</session-factory>
</hibernate-configuration>
InformationAccessRequest.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="MyProject.Model"
assembly="MyProject">
<class name="InformationAccessRequest" table="INFO_ACCESS_REQS">
<id name="Id" column="INFO_ACCESS_REQS_ID">
<generator class="native">
<param name="sequence">INFO_ACCESS_REQS_ID_SEQ</param>
</generator>
</id>
<property name="Assembly" column="ASSEMBLY_DT" />
<property name="Requested" column="INFO_ACCESS_REQ_DT" />
<property name="Expiration" column="INFO_ACCESS_REQ_EXP_DT" />
<property name="RequesterIdentification" column="INFO_ACCESS_REQ_USR_ID" />
<property name="Number" column="INFO_ACCESS_REQ_NUM" />
<property name="Reception" column="INFO_ACCESS_REQ_RECEP_DT" />
<property name="Creator" column="CREATOR_ID" />
<property name="Created" column="CREATED_DT" />
<property name="Updater" column="UPDATER_ID" />
<property name="Updated" column="UPDATED_DT" />
<property name="Deleted" column="DELETED_DT" />
</class>
</hibernate-mapping>
ISession.SaveOrUpdate()
var newRequest = new InformationAccessRequest();
newRequest.Requested = DateTime.Today.AddDays(-1);
newRequest.Expiration = DateTime.Today.AddDays(1);
newRequest.Reception = DateTime.Today;
newRequest.Number = Guid.NewGuid().ToString().Substring(0, 12);
newRequest.RequesterIndentification = Guid.NewGuid()..ToString().Substring(0, 10);
newRequest.Created = DateTime.Today;
newRequest.Creator = User.Current.Login;
newRequest.IsNew = true;
newRequest.IsDirty = true;
session.SaveOrUpdate(newRequest);
All required information provided, and all database constraints respected. The SaveOrUpdate() only does the select against the sequence, and no other sql instruction gets performed.
I just discovered a new feature of NHibernate working with identity-type, though it is an old feature actually.
Based on this article: NH2.1.0: New generators, I shall specify the generator class as sequence-identity.
From that new generators article, here's the plain explanation:
sequence-identity
The “sequence-identity” is based on “sequence” but work as an “identity”. The POID values is retrieved with the INSERT query. The types, in your entity, maybe are System.Int32 or System.Int64 depending on your RDBMS sequence generator.
The query that run in ORACLE is:
INSERT INTO my_entity (id, name)
VALUES (hibernate_sequence.nextval, :p0) returning id into :nhIdOutParam
The “hibernate_sequence” is the default name for a sequence where no alternative name is provided trough the mapping. As you can see, in this case, the “sequence” are working like “identity”, the value of the POID is retrieved immediately and the generator has the same problem of “identity”.
InformationAccesRequest.hbm.xml (updated accordingly)
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="MyProject.Model"
assembly="MyProject">
<class name="InformationAccessRequest" table="INFO_ACCESS_REQS">
<id name="Id" column="INFO_ACCESS_REQS_ID">
<generator class="sequence-identity">
<param name="sequence">INFO_ACCESS_REQS_ID_SEQ</param>
</generator>
</id>
<property name="Assembly" column="ASSEMBLY_DT" />
<property name="Requested" column="INFO_ACCESS_REQ_DT" />
<property name="Expiration" column="INFO_ACCESS_REQ_EXP_DT" />
<property name="RequesterIdentification" column="INFO_ACCESS_REQ_USR_ID" />
<property name="Number" column="INFO_ACCESS_REQ_NUM" />
<property name="Reception" column="INFO_ACCESS_REQ_RECEP_DT" />
<property name="Creator" column="CREATOR_ID" />
<property name="Created" column="CREATED_DT" />
<property name="Updater" column="UPDATER_ID" />
<property name="Updated" column="UPDATED_DT" />
<property name="Deleted" column="DELETED_DT" />
</class>
</hibernate-mapping>
Related
I'm using a mapping with a filter.
But when I try to persist my object, it first wants to get a snapshot (because my Id is a string).
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="DomainLayer.General" assembly="DomainLayer">
<class name="Fund" table="OPENA_BriefW" lazy="false">
<id name="Id" column="`BRWNUMMER`" type="string" >
</id>
<property name="Name" column="`BRWNAAM`" type="string" />
<property name="Contact" column="`BRWNAAM2`" type="string" />
<property name="Address" column="`BRWADRES`" type="string" />
<property name="City" column="`BRWSTAD`" type="string" />
<property name="Zip" column="`BRWPOST`" type="string" />
<property name="Phone" column="`BRWTELEFOON`" type="string" />
<property name="Fax" column="`BRWTELEFAX`" type="string" />
<property name="Iban" column="`brw_iban`" type="string" />
<property name="BankAccount" column="`BRWBANKNU`" type="string" />
<property name="Swift" column="`brw_swift`" type="string" />
<property name="ReceiveOffice" column="`BRWONTVKANT`" type="int" />
<property name="RegionDirection" column="`BRWGEWESTDIR`" type="int" />
<many-to-one name="Country" class="DomainLayer.General.CodeDescription" fetch="join" not-found="ignore">
<formula>'ALG'</formula>
<formula>'0'</formula>
<formula>'WG030'</formula>
<column name="`BRWLAND`" />
<formula>:LanguageFilter.Id</formula>
</many-to-one>
</class>
</hibernate-mapping>
As you can see the filter :LanguageFilter.Id is the one causing the troubles. When I do a normal .List() it doesn't cause problems.
But when I persist, it first wants to check if the Fund already exists yes/no. By doing a .Get (with the Id).
Then I get the error can't retrieve snapshot because in my query he doesn't replace :LangaugeFilter.Id with the effective value i set on my session.
I enable the filter on my session like so:
session.EnableFilter("LanguageFilter").SetParameter("Id", 1);
Here's the filter-def mapping:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<filter-def name='LanguageFilter' >
<filter-param name='Id' type='System.Int32' />
</filter-def>
</hibernate-mapping>
This post (comment 4) http://ayende.com/blog/3993/nhibernate-filters says that session.Get and Load ignores filters.
Are there any alternatives, because I need that language to be variable.
Ok, what I did was the following: I didn't use SaveOrPersist, but Save when new and Persist when I had an old one. This didn't execute the extra get.
I have research about it and all solution are pointing out to one solution, to use the libe of codes below and put it int .hbm.xml file. but I do not have one. What I have is hibernate.cfg.xml and nhvalidator.cfg.xml.
I have read from here : http://forums.asp.net/t/1407518.aspx/1
but where can I type the query tags? I typed it in the hibernate.cfg.xml (see below) but it is not working.
<?xml version="1.0" encoding="utf-16"?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">Server=localhost\MSSQLSERVERR2;Database=SupplierSearch;Trusted_Connection=True</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
<property name="cache.use_minimal_puts">false</property>
<property name="use_outer_join">false</property>
</session-factory>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Quarry.SupplierSearch"
assembly="SupplierSearch">
<class name="SupplierSearch" table="Client" lazy="true">
<id name="ClientId" column="ClientId">
<generator class="native" />
</id>
<property name="FirstName" column="FirstName" />
<property name="ClientId" column="ClientId" />
<loader query-ref="GetAllClient"/>
</class>
<sql-query name="GetAllClient" >
<return alias="GetAllClient" class="SupplierSearch">
<return-property name="ClientId" column="ClientId"/>
<return-property name="FirstName" column="FirstName"/>
</return>
exec GetAllClient
</sql-query>
</hibernate-mapping>
</hibernate-configuration>
since it is not working, I tried typing it in my Client.nhv.xml (see below) where client is mapped)
<?xml version="1.0" encoding="utf-8"?>
<nhv-mapping assembly="Quarry.SupplierSearch" namespace="Quarry.SupplierSearch.Model" xmlns="urn:nhibernate-validator-1.0">
<class name="Client">
<property name="Address1">
<not-null />
</property>
<property name="Address2">
<not-null />
</property>
<property name="BusinessName">
<not-null />
</property>
<property name="ClientId">
<not-null />
<digits integerDigits="10" />
</property>
<property name="Country">
<not-null />
</property>
<property name="FirstName">
<not-null />
</property>
<property name="LastName">
<not-null />
</property>
<property name="ListingType">
<not-null />
<digits integerDigits="10" />
</property>
<property name="PhoneNumber">
<not-null />
</property>
<property name="PostCode">
<not-null />
</property>
<property name="State">
<not-null />
</property>
</class>
<loader query-ref="GetAllClient"/>
<sql-query name="GetAllClient">
<load-collection alias="Clients" role ="Client"/>
exec [GetAllClient]
</sql-query>
</nhv-mapping>
any suggestion to get this working? thanks
It needs to be called Client.hbm.xml not Client.hbv.xmland an embedded resource.
edit I not familiar with any tools that generate hbv nor have I ever seen a mapping that begins with <nhv-mapping .. >. There must be a custom plugin/dll that you must use to get this working. What tool are you using?
However have you seen this blog post to get SP's to work without any custom tools.
using stored procedures is not supported with load-collection (scalar only).
use the format:
<sql-query name="GetAllClient">
<load-collection alias="Clients" role ="Client"/>
SELECT {c.*}
FROM client c
</sql-query>
(with the stuff in your stored proc innards in place of the "SELECT...FROM..." part of course.)
I have a named query in my nhibernate mapping file, and every time i run the code, it fails to create the Session with the message 'Errors in named queries', and no inner exception or anything else pointing to what's wrong with my named query. I'm very new to using nhibernate but am sure i have everything set up correctly (i.e. mapping file is an embedded resource, and the classes used in the query are mapped correctly).
Could anyone suggest if there is an obvious error or problem with my mapping file which may cause this error.
Mapping file:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
schema="SmokefreeServices.Common"
assembly="Model.Smoking"
namespace="Model.Smoking">
<class name="CommonReferral" table="Referral">
<id name="ID">
<generator class="identity" />
</id>
<property name="PatientID" />
<property name="Module" />
<property name="OriginalModule" />
<property name="ReferralSource" />
<property name="OtherReferralSource" />
<property name="ReferralDate" />
<property name="ReferralComments" />
<property name="CreatedBy" />
<property name="CreatedDate" />
<property name="UpdatedBy" />
<property name="UpdatedDate" />
<one-to-one name="Status" class="CurrentReferralStatus" fetch="join" />
<bag name="StatusHistory" inverse="true" lazy="true">
<key column="ReferralID" />
<one-to-many class="ReferralStatus" />
</bag>
</class>
<query name="GetOpenReferral">
<![CDATA[
from CommonReferral ref
inner join fetch ref.Status referralStatus
where ref.PatientID = :patientId
and referralStatus.IsResolved = 0
]]>
</query>
</hibernate-mapping>
Is it a mapping problem between CommonReferal and CurrentReferralSttaus. Out of interest what happens if you try this:-
<query name="GetOpenReferral">
<![CDATA[
from CommonReferral ref
where ref.PatientID = :patientId
]]>
</query>
Another quick thought can you try and remove the keyword fetch as one-to-one are always eager fetched anyway.
<query name="GetOpenReferral">
<![CDATA[
from CommonReferral ref
inner join ref.Status referralStatus
where ref.PatientID = :patientId
and referralStatus.IsResolved = 0
]]>
</query>
This is my mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainModel" namespace="DomainModel">
<class name="Cliente" table="Clientes">
<id name="Id" column="ID" type="guid" unsaved-value="00000000-0000-0000-0000-000000000000">
<generator class="guid.comb" />
</id>
<property name="Nombre" column="Nombre" not-null="true" type="string" length="50" />
<property name="Direccion" column="Direccion" not-null="false" type="string" length="75" />
<property name="Telefono" column="Telefono" not-null="false" type="string" length="15" />
<property name="Email" column="Email" not-null="false" type="string" length="50" />
<property name="FechaAlta" column="FAlta" not-null="true" type="DateTime" />
<list name="Pedidos" cascade="all-delete-orphan">
<key column="Cliente"/>
<index column="FechaEntrada"/>
<one-to-many class="Pedido"/>
</list>
</class>
</hibernate-mapping>
When I try to insert a new "Cliente" (Customer) into the database, NHibernate generates instead an UPDATE command, looking to update the customer with ID 00000000-0000-0000-0000-000000000000. As it doesn't exist, I naturally get the exception:
InnerException: NHibernate.StaleStateException
Message="Unexpected row count: 0; expected: 1"
I'm guessing this has to be a noob NHibernate error, but I've been checking my mapping with other guid example mappings on the web and I don't see my mistake.
Thanks!
PS.- As requested, this is the code I use to do the insertion:
// METHOD 1
//BusinessTransaction bt = new BusinessTransaction(sesion);
//bt.Attach(cliente);
//bt.Commit();
//bt.Close();
// METHOD 2
ITransaction trans = sesion.BeginTransaction();
sesion.SaveOrUpdate(cliente);
trans.Commit();
sesion.Flush();
sesion is an ISession object created with the BuildSessionFactory method of NHibernate Configuration. Method 1 provokes the error described, an UPDATE when it should be doing an INSERT, while Method 2 works, however (I'm an idiot and was looking a the wrong database file).
PPS.- For the sake of testing I've changed the guid fields for auto-incremented int identity fields, but my problems are the same.
PPPS.- Just in case, this are the contents of my hibernate.cfg.xml file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
<property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
<property name="connection.connection_string">Data Source=Pedidos.sdf</property>
<property name="show_sql">true</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
<mapping assembly="PedidosDomainModel" />
</session-factory>
</hibernate-configuration>
Here's one solution: Map the Id (that's how I map it in Fluent, you will sort out an equivalent in XML mapping)
Id(x => x.Id).GeneratedBy.Assigned();
And before saving you can do something like this:
if (client.HasEmptyId())
{
client.Id = Guid.NewGuid();
session.Save(client);
}
else
{
session.Update(client);
}
I have the following mapping
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="Domain.Core"
namespace="Domain.Core">
<class name="Industry" table="INDUSTRY">
<cache usage="read-write" region="IndustryCache" include="all"/>
<id name="Id" type="Int64" column="IID">
<generator class="sequence">
<param name="sequence">INDUSTRY_SEQ</param>
</generator>
</id>
<version name="Version" column="VERSION" access="property" unsaved-value="null" generated="never"/>
<property name="CreationTime" column="CREATE_DATE" type="DateTime" not-null="true" />
<property name="CreatedBy" column="CREATE_USER" type="String" not-null="true" />
<property name="LastUpdateTime" column="MODIFY_DATE" type="DateTime" not-null="false" />
<property name="LastUpdateBy" column="MODIFY_USER" type="String" not-null="false" />
<property name="Code" column="INDUSTRY" type="String" not-null="false" />
<map name="Resources" table="INDUSTRY_TL" fetch="subselect">
<cache region="IndustryCache" usage="read-write" include="all"/>
<key column="INDUSTRY_ID"/>
<composite-index class="Framework.Globalization.UILanguage, Framework">
<key-property name="Code" column="LANG" access="property" />
</composite-index>
<composite-element class="Industry+Translation">
<property name="Name" column="Industry_TL" />
</composite-element>
</map>
</class>
<query name="GetIndustyOrderByName">
<![CDATA[
from Industry as i left join fetch i.Resources as res where INDEX(res) = :lang order
by res.Name
]]>
</query>
</hibernate-mapping>
and the following configuration in hibernate.cfg.xml
<property name="show_sql">true</property>
<property name='proxyfactory.factory_class'>NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
<property name='prepare_sql'>true</property>
<property name='query.substitutions'>Y=true,N=false,0=false,1=true</property>
<property name="generate_statistics">true</property>
<property name="cache.use_second_level_cache">true</property>
<property name="cache.provider_class">Framework.Cache.SossCacheProvider, Framework.Cache.SossNHibernateCache</property>
<property name="cache.use_query_cache">true</property>
Now when I run the named query with SetCacheable(true) which calls out to the mapped collection, it doesn't get to the 2nd level cache. Is there any reason why?
More Generically, is there a way to put the resultset of a named query into second level cache?
Thanks!
In order for queries to use the second level cache, two things must be done:
1. Enable the query cache in the NHibernate config:
<property name="cache.use_query_cache">true</property>
2. Enable the query for caching when getting the IQuery instance:
IQuery = session.GetNamedQuery("from Industry as i left join fetch i.Resources as res where INDEX(res) = :lang order by res.Name")
.SetCacheable(true)
.Setxxx();
These settings cause the results of the queries to be put in the second level cache. But the second level query cache stores only identifiers of entities, not the entities themselves. In order for the execution of the query to avoid the database completely, the entities must be cached as well. See the NH Docs for more explanation of the interaction of the second level caches.