Search code examples
nhibernatenhibernate-mapping

Cascade Save - StaleObjectStateException: Row was updated or deleted by another transaction


Having an issue with updating the NHibernate version. Current version is 3.3.1.4000 and trying to update to 4. After updating the unit test which does save with cascade fails with:

NHibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [NHibernateTests.TestMappings.ProductLine#cdcaf08d-4831-4882-84b8-14de91581d2e]

The mappings:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
    <class name="Product" lazy="false" table="UserTest">
        <id name="Id">
            <generator class="guid"></generator>
        </id>

        <version name="Version" column="Version" unsaved-value="0"/>
        <property name="Name" not-null="false"></property>
        <property name="IsDeleted"></property>

        <bag name="ProductLines" table="ProductLine" inverse="true" cascade="all" lazy="true" where="IsDeleted=0" >
            <cache usage="nonstrict-read-write" />
            <key column="UserId" />
            <one-to-many class="ProductLine"  />
        </bag>
    </class>
</hibernate-mapping>


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
    <class name="ProductLine" where="IsDeleted=0" lazy="false">
        <cache usage="nonstrict-read-write" />
        <id name="Id">
            <generator class="guid"></generator>
        </id>

        <version name="Version" column="Version" unsaved-value="0"/>
        <property name="IsDeleted"></property>

        <many-to-one name="Product" class="Product" column="UserId" not-null="true" lazy="proxy"></many-to-one>

    </class>
</hibernate-mapping>

Classes:

public class Product
{
    public Guid Id { get; set; }
    public int Version { get; set; }
    public bool IsDeleted { get; set; }

    public string Name { get; set; }
    public bool IsActive { get; set; }
    public IList<ProductLine> ProductLines { get; private set; }

    public Product()
    {
        ProductLines = new List<ProductLine>();
    }
}

public class ProductLine
{
    public Guid Id { get; set; }
    public int Version { get; set; }
    public bool IsDeleted { get; set; }

    public Product Product { get; set; }
}

The test:

[TestMethod]
public void CascadeSaveTest()
{
    var product = new Product
    {
        Id = Guid.NewGuid(),
        Name = "aaa",
        IsActive = true
    };
    var productLine = new ProductLine
    {
        Id = Guid.NewGuid(),
        Product = product,
    };
    product.ProductLines.Add(productLine);

    using (var connection = new RepositoryConnection())
    {
        using (var repositories = new Repository<Product>(connection))
        {
            repositories.Create(product);
            //the below just calls the Session.Transaction.Commit();
            connection.Commit();   //NH3.3.1.400 passes, NH4 fails
        }
    }
}

Thanks for you ideas in advance.


Solution

  • Drilling it further down turned out that NHibernate4 has problems identifying whether it is a new entity or an already existent when concerned with Cascade. With the scenario in question it was calling a SQL Update for the ProductLine, rather than Create.

    It works fine with the below changes, however I'm quite puzzled with such a changes in between NHibernate versions.

    Change to ProductLine Mapping

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTests" namespace="NHibernateTests.TestMappings">
        <class name="ProductLine" where="IsDeleted=0" lazy="false">
            <cache usage="nonstrict-read-write" />
            <!-- here comes the updated line -->
            <id name="Id" type="guid" unsaved-value="00000000-0000-0000-0000-000000000000">
                <generator class="guid"></generator>
            </id>
    
            <version name="Version" column="Version" unsaved-value="0"/>
            <property name="IsDeleted"></property>
    
            <many-to-one name="Product" class="Product" column="UserId" not-null="true" lazy="proxy"></many-to-one>
    
        </class>
    </hibernate-mapping>
    

    Change to test method

    [TestMethod]
    public void CascadeSaveTest()
    {
        var product = new Product
        {
            Id = Guid.NewGuid(),
            Name = "aaa",
            IsActive = true
        };
        var productLine = new ProductLine
        {
            Id = Guid.Empty,            //The updated line
            Product = product,
        };
        product.ProductLines.Add(productLine);
    
        using (var connection = new RepositoryConnection())
        {
            using (var repositories = new Repository<Product>(connection))
            {
                repositories.Create(product);
                connection.Commit();
            }
        }
    }