I apologies if this has already been answered else where but although I've come across a lot of a material describing why the one-to-one mapping does not support lazy loading I've not found any concrete examples of working around this for what must be a very common pattern.
For performance reasons I want to split a number of my classes into the main class (lets call it the summary class) and a detail class that will be available through a property of the main or summary class. The idea being that many of these classes will simply be loaded for display in a grid where some of the (large) detail properties are not required, there will be situations however where the details for an individual item will be required and then I would like to go out to the database to get these individually (lazy load them).
So I set everything up with the one-to-one mapping as shown below (commented out) however as that does not perform a lazy loading of the detail class that is not what I want. If I try replacing the one-to-one with a many-to-one as I've read about I get an exception when trying to save a new item, exception is shown below.
Surely this is a very common pattern, how should I implement this?
Hibernate version:
2.0.1 GA
Mapping documents:
Code:
<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Test.Asset, Test" table="Assets">
<id name="Id" column="Id" type="Int32" unsaved-value="-1" access="nosetter.camelcase">
<generator class="identity"/>
</id>
<property name="Name" column="Name" type="String" length="64"/>
<!-- <one-to-one name="Detail" class="Test.AssetDetail, Test" cascade="all"/> -->
<many-to-one name="Detail" column="Id" class="Test.AssetDetail, Test" cascade="all"/>
</class>
<class name="Test.AssetDetail, Test" table="AssetDetails">
<id name="AssetId" column="AssetId" type="Int32" unsaved-value="-1" access="nosetter.camelcase">
<generator class="foreign">
<param name="property">Asset</param>
</generator>
</id>
<one-to-one name="Asset" class="Test.Asset, Test" constrained="true"/>
<property name="ABigProperty" column="ABigProperty" type="String" length="64"/>
</class>
</hibernate-mapping>
Code:Code:
using System;
using NHibernate;
using NHibernate.Cfg;
namespace Test
{
public class Asset
{
private int id;
private string name;
private AssetDetail detail;
public Asset()
{
id = -1;
detail = new AssetDetail();
detail.Asset = this;
}
public virtual int Id
{
get {
return(id);
}
}
public virtual string Name
{
get {
return(name);
}
set {
name = value;
}
}
public virtual AssetDetail Detail
{
get {
return(detail);
}
set {
detail = value;
}
}
}
public class AssetDetail
{
private int assetId;
private Asset asset;
private string aBigProperty;
public AssetDetail()
{
assetId = -1;
}
public virtual int AssetId
{
get {
return(assetId);
}
}
public virtual Asset Asset
{
get {
return(asset);
}
set {
asset = value;
}
}
public virtual string ABigProperty
{
get {
return(aBigProperty);
}
set {
aBigProperty = value;
}
}
}
public class Test
{
public static void Main(string[] arguments)
{
Configuration configuration;
ISessionFactory sessionFactory;
ISession session;
ITransaction transaction;
Asset asset;
configuration = new Configuration();
configuration.Configure();
sessionFactory = configuration.BuildSessionFactory();
session = sessionFactory.OpenSession();
transaction = session.BeginTransaction();
asset = new Asset();
asset.Name = "Test";
asset.Detail.ABigProperty = "Blah blah blah";
session.Save(asset);
transaction.Commit();
Console.WriteLine(asset.Id.ToString());
session.Close();
sessionFactory.Close();
}
}
}
Full stack trace of any exception that occurs:Code:
Unhandled Exception: NHibernate.Id.IdentifierGenerationException: null id generated for:Test.AssetDetail
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean req
uiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.SaveOrUpdate(String entityName, Object obj)
at NHibernate.Engine.CascadingAction.SaveUpdateCascadingAction.Cascade(IEventSource session, Object child, String entityName, Object anything, Boolean isCasc
adeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeToOne(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeAssociation(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeProperty(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled)
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything)
at NHibernate.Event.Default.AbstractSaveEventListener.CascadeBeforeSave(IEventSource source, IEntityPersister persister, Object entity, Object anything)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityCol
umn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSave(Object entity, Object id, IEntityPersister persister, Boolean useIdentityColumn, Object any
thing, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean req
uiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
at Test.Test.Main(String[] arguments) in Test.cs:line 119
Name and version of the database you are using:Microsoft SQL Server 2005
Code:
create table dbo.Assets (
Id int identity(1, 1) not null,
Name nvarchar(64) not null,
constraint PK_Assets primary key (Id) )
go
create table dbo.AssetDetails (
AssetId int not null,
ABigProperty nvarchar(64) not null,
constraint PK_AssetDetails primary key (AssetId),
constraint FK_AssetDetails_Assets foreign key (AssetId) references dbo.Assets (Id) )
go
The generated SQL (show_sql=true):
None
Debug level Hibernate log excerpt: