views:

622

answers:

3

Hi,

I am then using Fluent NHibernate and its automapping feature to map the the following simplified POCO classes:

public class Webpage
{    
public virtual int Id { get; set; }    
public virtual string UrlIdentifier { get; set; }    
public virtual WebpageType WebpageType { get; set; }    
}

public class WebpageType
{    
public virtual int Id { get; set; }    
public virtual string Name { get; set; }       
}

I am then overriding the following mapping to explicitly set no cascading from Webpage to WebpageType:

public class WebpageMap : IAutoMappingOverride<Webpage>
{
    public void Override(AutoMapping<Webpage> mapping)
    {
        mapping.References(w => w.WebpageType).Cascade.None();    
    }
}

For any pur NHibernate readers, here are the xml mappings produced by fluent:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="EveryPage.Core.Domain.Webpage, EveryPage.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`Webpage`">
    <id name="Id" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" unsaved-value="0">
      <column name="Id" />
      <generator class="identity" />
    </id>
    <property name="UrlIdentifier" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="UrlIdentifier" />
    </property>
    <many-to-one cascade="none" class="EveryPage.Core.Domain.WebpageType, EveryPage.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="WebpageType">
      <column name="WebpageType_id" />
    </many-to-one>    
  </class>
</hibernate-mapping>


<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="property" auto-import="true" default-cascade="none" default-lazy="true">
  <class xmlns="urn:nhibernate-mapping-2.2" name="EveryPage.Core.Domain.WebpageType, EveryPage.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="`WebpageType`">
    <id name="Id" type="System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" unsaved-value="0">
      <column name="Id" />
      <generator class="identity" />
    </id>
    <property name="Name" type="System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="Name" />
    </property>
  </class>
</hibernate-mapping>

The problem comes when I test that updates do not cascade to WebpageType via webpage, basically they do!!

I have the following test:

    [Test]
    public void Assert_SaveOrUpdate_On_Webpage_Does_Not_Cascade_Update_To_WebpageType()
    {
        // Get the existing webpage.
        webpage = _webpageRepository.Get("~/testwebpage1.aspx");

        // Update the WebpageType.
        const string webpageTypeName = "qwerty test";
        webpage.WebpageType.Name = webpageTypeName;

        // Save the changes.
        Assert.DoesNotThrow(() => _webpageRepository.SaveOrUpdate(webpage));

        // We need to flush the changes to the store for it to execute the changes.
        Assert.DoesNotThrow(() => NHibernateSession.Current.Flush());

        // Remove the webpage and tag from the level 1 cache so we force a trip to the store on our next check.
        NHibernateSession.Current.Evict(webpage);

        //  Check that the webpageType has not been updated.
        webpageType = _webpageTypeRepository.Get(webpageType.Id);
        Assert.AreNotEqual(webpageTypeName, webpageType.Name);
    }

The above test is wrapped in a global transaction.

The test fails and NHibernate does execute an update to the Name of the related WebpageType. The delete and save(create new) cascades work correctly and do not cascade.

Have I missunderstood cascade and/or is there a problem with my logic/test.

Any help/advice is appreciated. Thanks.

A: 

What does your repository do? Make sure it doesn't run a saveorupdate on the webpagetype. If it isn't then I don't see any obvious explanation for this behaviour.

Brian O'Connell
Hi Brian, no my repository is just saving the webpage.
j3ffb
+2  A: 

If you are trying to stop your app from accidentally changing properties on WebPageType, I think it would be easier and safer to achieve this by marking WebPageType as ReadOnly in the mapping. Then you won't need to protect it via handling cascading in all its associations.

Noel Kennedy
Cheers Noel, I will try that out :)
j3ffb
+1  A: 

I think this is a misunderstanding of what cascading means.

In your example, NHibernate will update the Name property of you WebPageType no matter what you set cascading to. If you think about it, how would the NHibernate library tell if you're manipulating the property's value using the association from the WebPage instance, or if it's done "directly"?

The settings for cascading in NHibernate tells how associations between entities should be handled, not how the actual value inside each entity is handled. For example, you can set delete cascading, which will automatically delete associated entities when the entity itself is deleted.

Things blog post might make things a bit clearer, or at least work as some kind of reference: http://ayende.com/Blog/archive/2006/12/02/NHibernateCascadesTheDifferentBetweenAllAlldeleteorphansAndSaveupdate.aspx

Liedman
Hi Liedman, I understand what you are saying however, if i only save the Webpage object, why is NH also performing a save on the many-to-one association to WebpageType? Is that not a sort of cascade save??
j3ffb
This is one a the most common mistakes about how NHibernate works. In NHibernate, changes to persistent objects (i.e. instances you've loaded into a ISession from the DB using a query, get/load or just persisted using save) is *automatically* saved to the database (unless you rollback the transaction).From the docs: "Transactional persistent instances (ie. objects loaded, saved, created or queried by the ISession) may be manipulated by the application and any changes to persistent state will be persisted when the ISession is flushed" (flush i automatic unless you specify otherwise.)
Liedman