级联保存 - StaleObjectStateException:行已被另一个事务更新或删除

时间:2016-02-26 11:19:02

标签: nhibernate nhibernate-mapping

更新NHibernate版本时遇到问题。当前版本为3.3.1.4000并尝试更新为4。 更新单元测试后,使用级联保存失败:

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

映射

<?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>

类:

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; }
}

测试:

[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
        }
    }
}

提前感谢您的想法。

2 个答案:

答案 0 :(得分:1)

嗯,我想我现在已经明白了导致NH 4错误的原因。如果我是对的,那就是一个有点人为的案例,导致这种行为难以成为一个错误。

在您的示例中,产品系列通过bag进行映射。 bag可以包含重复项,这需要ProductProductLine之间的中间表。 (类似于ProductProductLine表格,其中包含UserId(ProductId)列和ProductLineId列。)

您已将此中间表设置为ProductLine表。我怀疑db的提交会导致NHibernate插入产品,然后是产品系列,然后通过再次插入ProductLine表来尝试插入关系。 (您可以通过在数据库上分析SQL查询来检查它。)

事情有点混乱,因为doc州(强调是我的):

  

table(可选 - 默认为属性名称)的名称   集合表(不用于一对多关联)

但是,如何尊重允许在集合中重复的bag语义?来自同一个文档:

  

行李是无序的,无索引的集合,可能包含相同的集合   元素多次。

无论如何,在您的示例中,您确实应该将one-to-many映射为set,如doc所示。

<?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>

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

并更改您的收藏类型以使用.Net fx 4 System.Collections.Generic.ISet<T>

public ISet<ProductLine> ProductLines { get; private set; }

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

如果这会导致你的麻烦消失,那就意味着NH {4}中的bag处理会发生一些变化。但我们是否应该将此变化视为一个错误?不确定,因为在这种情况下使用bag看起来不合适。

答案 1 :(得分:0)

进一步深入研究表明,NHibernate4在确定它是一个新实体还是在与Cascade有关时已经存在的问题上存在问题。 在有问题的情况下,它为Update而不是ProductLine调用SQL Create

下面的更改可以正常工作,但我对NHibernate版本之间的这种变化感到非常困惑。

更改为ProductLine映射

<?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>

更改为测试方法

[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();
        }
    }
}