级联保存 - StaleObjectStateException:行已被另一个事务更新或删除
Cascade Save - StaleObjectStateException: Row was updated or deleted by another transaction
更新 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
}
}
}
提前感谢您的想法。
好吧,我想我现在已经明白是什么导致了 NH 4 的错误。如果我是对的,那是一个有点人为的案例,导致这种行为很难被认定为错误。
在您的示例中,产品系列通过 bag
映射。 bag
可以包含重复项,这需要 Product
和 ProductLine
之间的中间值 table。 (类似于带有 UserId
(ProductId) 列和 ProductLineId
列的 ProductProductLine
table。)
您已将此中间 table 设置为 ProductLine
table。我怀疑对 db 的提交导致 NHibernate 插入产品,然后是产品线,然后尝试通过在 ProductLine
table 中再次插入来插入关系。 (您可以通过在您的数据库上分析 SQL 查询来检查这一点。)
事情有点混乱,因为 doc 状态(强调是我的):
table (optional - defaults to property name) the name of the
collection table (not used for one-to-many associations)
但是,如何遵守 collection 中允许重复的 bag
语义?来自同一文档:
A bag is an unordered, unindexed collection which may contain the same
element multiple times.
无论如何,在您的示例中,您确实应该将 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>
并更改您的 collection 类型以使用 .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
不适合我。
进一步深入研究发现 NHibernate4 在与 Cascade
相关时无法识别它是新实体还是已经存在的实体。
对于有问题的场景,它正在为 ProductLine
调用 SQL Update
,而不是 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();
}
}
}
更新 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
}
}
}
提前感谢您的想法。
好吧,我想我现在已经明白是什么导致了 NH 4 的错误。如果我是对的,那是一个有点人为的案例,导致这种行为很难被认定为错误。
在您的示例中,产品系列通过 bag
映射。 bag
可以包含重复项,这需要 Product
和 ProductLine
之间的中间值 table。 (类似于带有 UserId
(ProductId) 列和 ProductLineId
列的 ProductProductLine
table。)
您已将此中间 table 设置为 ProductLine
table。我怀疑对 db 的提交导致 NHibernate 插入产品,然后是产品线,然后尝试通过在 ProductLine
table 中再次插入来插入关系。 (您可以通过在您的数据库上分析 SQL 查询来检查这一点。)
事情有点混乱,因为 doc 状态(强调是我的):
table (optional - defaults to property name) the name of the collection table (not used for one-to-many associations)
但是,如何遵守 collection 中允许重复的 bag
语义?来自同一文档:
A bag is an unordered, unindexed collection which may contain the same element multiple times.
无论如何,在您的示例中,您确实应该将 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>
并更改您的 collection 类型以使用 .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
不适合我。
进一步深入研究发现 NHibernate4 在与 Cascade
相关时无法识别它是新实体还是已经存在的实体。
对于有问题的场景,它正在为 ProductLine
调用 SQL Update
,而不是 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();
}
}
}