I'm trying to get nHibernate to use second-level cache with a many-to-one relationship, however I can't find any clear explanation on how to set it up correctly. I found this How to get nhibernate to cache tables referenced via many-to-one - is my config correct?, but the example sJHonny provided is for one-to-many and it's not working for me when I adopt it. There are other posts going over this subject, but none of them are specific enough.
The XML config I provide works (I had to edit dramatically, so "hopefully" works), but lookup objects are being cached only when they are retrieved as DataObject is queried. I'd like to know 1) where/how to preload the LookupObject collection? 2) what if I assign this collection to a region and set expiration, then where/how do I reload the cache again? 3) how to change the DataObject's hbm.xml such that nHibernate doesn't generate a join with the LOOKUP table, i.e. such that lookup objects always come from the secondary cache, only getting the DATA.LOOKUP_ID from the db, not the LOOKUP.NAME?
LookupObject.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="BusinessObjects" assembly="BusinessObjects">
<class name="LookupObject" table="LOOKUP" mutable="false" batch-size="20">
<cache usage="read-only" />
<id name="Id" column="ID" />
<property name="Name" column="NAME" />
</class>
</hibernate-mapping>
DataObject.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="BusinessObjects" assembly="BusinessObjects">
<class name="DataObject"
table="DATA" mutable="false">
<composite-id>
<key-property name="Id" column="ID"/>
<key-property name="Date" column="DATE" type="Date"/>
</composite-id>
<many-to-one name="LookupObject" not-null="true" column="LOOKUP_ID" fetch="join">
</class>
</hibernate-mapping>
I believe I answered my own questions. 1) For preloading the entire collection, I just needed to change my code such that the entire list of lookups is always pulled from the DB and cached, then I get the individual object by ID with LINQ. 2) Same as before. 3) I haven't tested this yet, but I need to remove fetch="join" from many-to-one element because otherwise the SQL join will still be generated and data will be still returned. Then set the lookup object without nHibernate by getting it from cache.
Related
I'm trying to map a self-referencing table with NHibernate 3.2.0.4000. However, whenever I get an instance of DomainObject, it eagerly loads the subsequent versions. I'd rather not have to put an extra column my table, though that is an option.
Can I have NHiberante not eagerly load all of the subsequent versions without maintaining the relationship on both sides?
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping assembly="NHibernateHierarchyTest" namespace="NHibernateHierarchyTest" xmlns="urn:nhibernate-mapping-2.2">
<class name="DomainObject" table="DOMAIN_OBJECT" lazy="true" >
<id name="DomainObjectId" column="DOMAIN_OBJECT_ID">
<generator class="identity" />
</id>
<property name="Property">
<column name="PROPERTY" />
</property>
<many-to-one name="PreviousVersion" class="DomainObject" >
<column name="PREVIOUS_VERSION_DOMAIN_OBJECT_ID" />
</many-to-one>
<!--<many-to-one name="SubsequentVersion" class="DomainObject">
<column name="SUBSEQUENT_VERSION_DOMAIN_OBJECT_ID" />
</many-to-one>-->
<one-to-one name="SubsequentVersion" class="DomainObject" property-ref="PreviousVersion" />
</class>
</hibernate-mapping>
The one-to-one mapping will be always loaded eagarly with NHibernate. Not sure if this is a feature or bug, but that is how it works. If you need lazy load, use many-to-one or one-to-many. Not the best answer I know, but if you can add new column...
I am having some troubles crafting the HQL for a given nHibernate mapping file (nHibernate 1.2). This SQL Fiddle example shows the table structures and the results I'm wanting. Basically, I'll pass in a Jurisdiction and I want all Titles with no jurisdiction or that specific jurisdiction. The current process pulls everything back and filters in code; I'm working to improve this so it filters at the SQL level.
Mapping file looks very similar to this.
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyApp.Business"
namespace="Ap.Bus.Entity"
default-access="field.camelcase-underscore"
default-cascade="save-update" >
<class name="Titles" table="dbo.Titles" mutable="true" lazy="true">
<!--Primary Key-->
<id column="TitleID" name="Id" unsaved-value="0">
<generator class="identity">
</generator>
</id>
<property column="TITLE" name="Title"/>
<property column="Enabled" name="Enabled" />
<bag name="Jurisdictions" table="dbo.Jurisdictions" lazy="false" cascade="none">
<key column="TitleID" />
<many-to-many class="Ap.Shared.Jurisdiction, Ap.Shared" column="JurisdictionID"/>
</bag>
</class>
I've tried so many different HQLs and I could never get it to work when I started joining to the Jurisdictions. It was working when only returning enabled Titles without the join.
So, how can I write the HQL to accomplish this?
select t
from Title t left outer join t.Jurisdictions as j
where j is null or j = :someJurisdiction
Possibly you should add ".JurisdictionID" to the last two "j" above.
Having trouble with implementing second level cache in Nhibernate. I have a class mapped as follows:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Data" namespace="Data">
<class name="Account" table="Accounts" lazy="false">
<cache region="Standard" usage="read-write" include="all"/>
<id column="ID" name="ID">
<generator class="assigned" />
</id>
<version name="VersionStamp" column="VersionStamp" type="integer" unsaved-value="0" />
<property name="Name" not-null="true" />
<property name="Type" not-null="true" />
<property name="ClientID" not-null="true" insert="false" update="false" />
<property name="DateCreated" not-null="true" type="UtcDateTime" />
<property name="LastUpdatedDate" not-null="true" type="UtcDateTime" />
<property name="IsActive" not-null="true" />
<many-to-one name="Client" class="Client" column="ClientID" not-found="exception" not-null="true" />
</class>
</hibernate-mapping>
The property "ClientID" is a foreign key into the Clients table and the Client many-to-one property uses it to look up the associated client object.
When I add a new Account, I look up the Client object from the database with a Session.Get and assign it to my Account object's Client property. Behind the scenes, this also automatically populates the ClientID property when the object is written to the database, and the ID is correctly stored in the database. When I retrieve the Account object from the database by ID using Session.Get, all the fields are populated correctly when the object is retrieved.
However, when I implement the second level cache using the settings shown above, the ClientID property is NOT populated when the Account object is retrieved using Session.Get, but the Client property is populated correctly. Is there some reason why this will not work with second level cache? Or have I done something wrong in my mapping/configuration?
For now I am just using SysCache as my caching provider, and both query and second level cache are turned on. The Client class mapping contains a corresponding one-to-many property for the Accounts.
I like the convenience of having the ClientID property on my Account class, so that I can read it without using the Client property, and it seems to work fine without caching.
Thanks for any help.
Rich
I tried to reproduce your situation locally. With your mapping (used the same as the snippet above) I was able to get incorrect behaviour only on UPDATE. In that case, the ClientID was cached, and while the Client reference was changed, the ClientID remained unchanged. In other cases caching was working as expected.
The solution is to change the mapping. The below suggested mapping is the most suitable for read-only properties like ClientID. (I am using that approach as well).
<property name="ClientID" formula="[ClientId]"
not-null="true" insert="false" update="false" />
So the trick is in the formula mapping instead of Column (the default when none is provided)
My mapping (edited and dumbed down to protect the not-so-innocent):
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="MyAssembly, Culture=neutral"
namespace="MyAssembly" auto-import="false">
<class name="Customer" table="Customers">
<id name="Id" access="nosetter.camelcase-underscore" >
<generator class="assigned"/>
</id>
<property name="blah"/>
...
<property name="bleh"/>
<join table="Addresses" optional="true">
<key column="Id"/>
<component name="Address" class="MyAssembly.Address, MyAssembly, Culture=neutral" access="field.camelcase-underscore">
<property name="Street" />
<property name="Number" />
</component>
</join>
</class>
</hibernate-mapping>
When I do this:
Address dir = new Address();
dir.Street = "Foo";
dir.Number = 27;
//// Previously loaded customer
cli.Address = dir;
//// Save repository, commit transaction
That works fine, and automatically inserts the new address to the Address table.
BUT, if I want to delete the address:
//// Previously loaded customer with attached address
cli.Address = null;
//// Save repository, commit transaction
That, instead of deleting the row from the Addresses table NHibernate updates it, setting all its fields to null except Id.
What's wrong with my mapping?
Given your mapping, NHibernate performs correctly. The join-clause is for when a single object is to be spread over multiple tables. Typically there will always be one row in each table for the object.
Component mapping on the other hand is when you want a fine-grained object model, but store the data in the same table as the owning class. If the property is null, NHibernate can do nothing other than set all the columns used by the component to null. The fact that the component columns are the only non-key columns in the joined table is irrelevant.
Perhaps you are looking for one-to-one mapping instead?
http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-onetoone
I have an application that has a core assembly with base classes that I need to inherit from.. I need to save these to the database and after reading about NHibernate decided to use it.
However I have a problem with one of my new inherited classes.. I have setup the subclass map but when I save, it neither attempts to save any of it's base class properties or any of it's new ones that I have assigned in the mapping!
My classes are laid out like the following: (from a small demo app)
core assemblies
DataItem -> User
Anything that will touch the database inherits the DataItem class as it handles the id, modified date etc etc..
In my test I setup user to only have a FirstName..
If I save a new User it works great.. however when I inherit from user and then add another property called LastName and attempt to save this new object.. it only puts a sql statement together of INSERT INTO t_User (id) VALUES(?).. it doesn't attempt to save the first name or last name.. either though both have been set and are mapped.
My nhibernate.config:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory name="DAL">
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="adonet.batch_size">16</property>
<property name="current_session_context_class">web</property>
<property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
<mapping assembly="DAL"/>
<mapping assembly="NHibernateDemo"/>
</session-factory>
</hibernate-configuration>
As you can see I have 2 assemblies.. my DAL is my core and the NHibernateDemo is a web application that uses the core for inheritance.
My core DataItem mapping:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DAL" namespace="DAL.Model">
<class name="DataItem" table="t_DataItem" >
<id name="Id">
<generator class="native" />
</id>
<discriminator column="typeid" type="System.Int32"></discriminator>
<property name="IsActive" column="isActive" not-null="true" />
<property name="TypeId" column="typeId" not-null="true"></property>
<many-to-one name="Parent" column="ParentId" class="DataItem"></many-to-one>
<bag name="Children" cascade="all-delete-orphan">
<key column="ParentId"></key>
<one-to-many class="DataItem"/>
</bag>
<joined-subclass name="User" table="t_Users">
<key column="id"></key>
<property name="FirstName" column="firstName" not-null="true" ></property>
</joined-subclass>
<joined-subclass name="Email" table="t_Emails">
<key column="emailid"></key>
<property name="Address" column="Address"></property>
</joined-subclass>
</class>
</hibernate-mapping>
My inherited NewUser mapping that doesn't work!:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateDemo" namespace="NHibernateDemo.Model">
<subclass name="NewUser" extends="DAL.Model.User, DAL" discriminator-value="1">
<property name="LastName" column="LastName"></property>
</subclass>
Why is it that when I attempt to save my class NewUser that it doesn't attempt to save any of the other properties set, whether from it's base or newly declared properties?
I'd really appreciate any help or insight to this.. I must be missing something really simple and I just can't see it.
Thanks,
Mike
It could be that you're not able to mix subclass and joined-subclass mappings in the same class hierarchy. Since your DataItem and User are related by joined-subclass, you may need to make your NewUser class another joined-subclass of User.
Another issue might be the use of the "discriminator" in your current NewUser mapping. The discriminator should be an additional column in your User table that NHibernate uses to tell the difference between a User record and a NewUser record. I'm not sure if this works where the User base class might not specify its discriminator value, whereas the NewUser does. I'm not sure if you're specifying the discriminator-column anywhere, which might also be a problem.
I would suggest first trying to make NewUser a joined-subclass of User.