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.
Related
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>
I'm using NHibernate 3.2 with appfabric 1.1 for the 2nd level cache.
I’ve 2 classes mapped on the same table. The first class AFullEntity (MonitorLayoutData in the sample), inherit a second class which is lightweight class (MonitorLayout). MonitorLayoutData contains a heavy property that is not present in the base class. For example :
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Entities.MonitorLayout,Entities" table="MonitorLayouts2" lazy="false" schema="dbo" polymorphism="explicit">
<cache usage="nonstrict-read-write"/>
<id name="Id" column="MonitorLayout_ID" type="int">
<generator class="native" />
</id>
<property name="Name" column="MonitorLayout" type="string" />
<property name="UserId" column="User_ID" type="int" />
<property name="IsPublic" column="IsPublic" type="Boolean" not-null="true" />
<property name="ViewGuid" column="ViewGuid" type="string" not-null="true" />
<property name="TreeNode" column="TreeNode" type="string" />
<property name="IncludeNodeChildren" column="IncludeNodeChildren" type="Boolean" />
</class>
<class name="Entities.MonitorLayoutData,Entities" table="MonitorLayouts2" lazy="false" schema="dbo" polymorphism="explicit">
<cache usage="nonstrict-read-write"/>
<id name="Id" column="MonitorLayout_ID" type="int">
<generator class="native" />
</id>
<property name="Name" column="MonitorLayout" type="string" />
<property name="UserId" column="User_ID" type="int" />
<property name="IsPublic" column="IsPublic" type="Boolean" not-null="true" />
<property name="ViewGuid" column="ViewGuid" type="string" not-null="true" />
<property name="TreeNode" column="TreeNode" type="string" />
<property name="IncludeNodeChildren" column="IncludeNodeChildren" type="Boolean" />
<property name="LayoutData" column="LayoutData" type="BinaryBlob" not-null="false"/>
</class>
</hibernate-mapping>
Those classes use an explicit polymorphism to retrieve only entities for the selected type like note in the documentation : “Explicit polymorphism is useful when two different classes are mapped to the same table (this allows a "lightweight" class that contains a subset of the table columns)”.
However I got a problem when entities are cached. When I update a AFullEntity changes are not report in the lightweight class and this is a big problem for us.
I try other mechanism like subclass or extends but NHibernate force me to declare a discriminator element, which is not required for me.
Is there a way to do this ?
No, they are two separate objects, each with their own identity in the cache. Possible workaround would be to disable caching for lightweight objects or evict the lightweight object from the cache when the heavy object is loaded. Loading the heavy and light objects in the same session seems to me to defeat the purpose.
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.
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);
}
Firstly I am relatively new to NHibernate. Got two tables on TaxMapping and Address. A single TaxMapping must have one address and one address can belong to more than one Tax Mapping. They are linked through foreign key
TaxMapping hbm
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ITAPDTO" assembly="ITAPDTO">
<class name="TaxMapping" table="tblTaxMapping">
<id name="Tax_Mapping_ID">
<column name="Tax_Mapping_ID" sql-type="bigint" not-null="true"/>
<generator class="identity" />
</id>
<property name="Tax_ID" />
<property name="Client_Code" />
<property name="NRA_Sub_Account" />
<property column="Domicile" type="String" name="Domicile" length="5" />
<many-to-one name="Address" column="AddressID" cascade="none" not-found="exception" not-null="true" fetch="join" class="ITAPDTO.Address,ITAPDTO" />
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="ITAPDTO" assembly="ITAPDTO">
<class name="Address" table="Address">
<id name="AddressID" column="AddressID" type="Int32" unsaved-value="0">
<generator class="identity"/>
</id>
<property column="Client" type="String" name="Client" not-null="true" length="100" />
<property column="Contact" type="String" name="Contact" not-null="true" length="50" />
<property column="Address1" type="String" name="Address1" not-null="true" length="100" />
<property column="Address2" type="String" name="Address2" not-null="true" length="100" />
<property column="Address3" type="String" name="Address3" not-null="true" length="100" />
<property column="City" type="String" name="City" not-null="true" length="50" />
<property column="State" type="String" name="State" not-null="true" length="50" />
<property column="PoBox" type="String" name="PoBox" not-null="true" length="50" />
<property column="PostCode" type="String" name="PostCode" not-null="true" length="20" />
<property column="Country" type="String" name="Country" not-null="true" length="50" />
<property column="InsertedBy" type="String" name="Modified_By" length="20" />
<property column="InsertedOn" type="DateTime" name="Modified_Date" />
<property column="ConfirmedBy" type="String" name="Approved_By" length="20" />
<property column="ConfirmedOn" type="DateTime" name="Approved_Date" />
<property column="Status" type="String" name="Status" />
<property column="IUD" type="String" name="IUD" />
</class>
</hibernate-mapping>
I can pull in the data and bind to grid view with no issue. However when I update the Taxmapping the AddressID for the Address oject is always null event thoughh the other address fields are correctly populated and I dont know why. I currently have a hack in place the pulls the id from the db before I call update but I really shouldn't have to do this. Any thoughts would be welcome
regards
Is this due to cascade being set to "none" on the Taxmapping object's Address property? Try changing it to "save-update". You may also want to read the nhibernate documentation to figure out which cascade setting would be best for your situation, though it sounds like "save-update" would be most appropriate.
In addition to Krazzy's point, you should also check that your sessions are closed and / or flushed appropriately. A long-running session which is never flushed can exhibit similar problems.