更新包异常

Updating a bag exception

我有这样的数据库表:

[tblRecord]

RecordId    MyColumn1   MyColumn2  
----------------------------------
112         somedata8   somedata7   
112         somedata6   somedata1   
148         somedata3   somedata5

[tblRecordFruit]

RecordId    FruitTypeId  
-------------------------
  112         53  
  112         85  
  148         16  

[tblFruitType]

FruitTypeId     Text  
----------------------
53              Apple  
85              Banana  
16              Orange  

和相应的 NHibernate 映射:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    <class name="Record, Infrastructure.Interface"
           table="tblRecord">
        <id name="Id" type="Int32" unsaved-value="null">
            <column name="RecordId" length="4" sql-type="int" not-null="true" unique="true" index="PK_tblRecord" />
            <generator class="native" />
        </id>
        ...
        <bag name="Fruits" table="tblRecordFruit" inverse="false" lazy="true" cascade="save-update">
            <key>
                <column name="RecordId" length="4" sql-type="int" not-null="true" />
            </key>
            <many-to-many
                class="FruitType, Infrastructure.Interface">
                <column name="FruitTypeId" length="2" sql-type="smallint" not-null="true" />
            </many-to-many>
        </bag>
        ...
    </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="FruitType, Infrastructure.Interface"
         table="tblFruitType"
         mutable="false"
         lazy="false">
    <id name="Id" type="Int16" unsaved-value="0">
      <column name="FruitTypeID" sql-type="smallint" not-null="true" unique="true" index="PK_Fruit" />
      <generator class="native" />
    </id>
    <property name="Text" type="String">
      <column name="Text" length="255" sql-type="varchar" not-null="true" />
    </property>
  </class>
</hibernate-mapping>

和 C# 代码使用 NHiberate 获取和 Save/SaveOrUpdate/Merge 数据:

public Interface.SortableBindingList<T> GetByCriteria (NHibernate.ICriteria criteria)
{
    System.Collections.Generic.IList<T> myList = criteria.List<T>();
    SortableBindingList<T> mySortableList = ConvertToGenericBindingList(myList);
    return mySortableList;
}

using (NHibernate.ITransaction tx = this.session.BeginTransaction())
{
    // this.session.Save (this.Record);
    // this.session.SaveOrUpdate (this.Record);
    this.session.Merge(this.Record);
    tx.Commit(); // Exception here
}

因此用户创建了一条记录(或打开一条没有 FruitTypes 的记录)并添加了一些水果类型,然后保存。一切都很好。

然后用户再次打开该记录并向其添加另一个 FruitTypeRecord class 定义了一个集合:

public virtual System.Collections.Generic.IList<FruitType> Fruits

然后像这样添加一个水果:

Record.Fruits.Add(fruitType);

保存时抛出异常:

Violation of PRIMARY KEY constraint 'PK_tblRecordFruit_1'.
Cannot insert duplicate key in object 'dbo.tblRecordFruit'. The duplicate key value is (112, 53).

似乎是将另一行 (112,53) 插入 tblRecordFruit 而不是更新它。 MyColumn1MyColumn2tblRecord 中的任何数据都可以正常更新。

正如您在 C# 代码中看到的那样,我已经尝试了 .Save().SaveOrUpdate().Merge(),并且都导致了相同的异常。

NHibernate 版本 4.0.0.4000

我该怎么做?

如果我理解正确的话,您正在尝试更新 Record object 与 session 断开连接。因此,您将 Merge 用于 session re-connection.

在这种情况下,要使 Merge 为您的 collection 工作,他们需要 merge 映射中的级联选项。因此,请尝试更新您的 Fruits 映射: cascade="merge,save-update" 或简单地使用 cascade="all" 来涵盖所有情况:

<bag name="Fruits" table="tblRecordFruit" inverse="false" lazy="true" cascade="all">       

P.S。 SaveOrUpdate 调用实际上应该与您现有的 Fruits 映射 cascade="save-update" 一起正常工作。也许您在更新映射之前已经尝试过,否则请确保异常相同并提供 Fruit.

的映射

更新 1 如前所述,我假设您正在尝试更新分离的实体(从不同 session 加载的实体)。请确认并提供详细信息,究竟是如何发生的? UI 上显示的状态是 serialized/de-serialized 吗?

在这种情况下,您应该知道在保存 object 之后需要序列化状态。因为在保存 Fruits collection 类型更改为内部 NHibernate collection 之后,正确处理进一步的更新是必需的。因此,当为现有的 Record object record.Fruits 调用 record.Fruits.Add(obj) 时,它不能是 List<T> 类型,它应该是某种 NHibernate 类型(据说是 PersistangGenericType<T>) .

所以请确保您没有覆盖现有 object 的 collection 属性:

record.Fruits = new List<FruitType>();// WRONG for existing record...

//Instead clear existing record collection:
record.Fruits.Clear(); //Correct

同时在调试器中进行以下检查:

//You somehow obtain record instance that you want to update
var record = DeserializeOrLoadState(recordId);

...//When you update existing record
record.Fruits.Add(fruitType1);// Make sure in debugger that for existing record record.Fruits is NHibernate type PesistentGenericBag<T> and not List<T>

...//When you SaveOrUpdate/Merge existing record
session.Merge(record);// <- make sure in debugger that for existing record.Fruits is NHibernate type PesistentGenericBag<T> and not List<T>

...
SerializeState(record);// serialize state only after session.SaveOrUpdate

更新 2

It is not a PersistentGenericBag. This used to work. Is it possible that a change from NHibernate v2 to v4 now causes this problem?

我不知道它是如何在 NHibernate v2 中完成的 - 但肯定可以在您的代码中修复它。所以你需要调查为什么它仍然是 List - 这表明你做错了什么。 一些建议:

1) 如果您的 Fruits 属性 有 setter - 删除它以确保所有操作都是通过 IList 方法完成的:

public class Record
{
    public virtual IList<FruitType> Fruits {get; } = new List<FruitType>()
}

2) 调查这部分Then the user opens that record again and adds another FruitType to it.。你还没有说它是如何实现的。 UI <-> 实体映射在这里是如何实现的?当从数据库加载现有实体时,我很确定它不是 List 并且属于 NHibernate 类型。所以你应该调试并找到它更改为 List.

的地方

3) 同时尝试将级联设置更改为 cascade="all-delete-orphan"。我认为这不会改变任何东西 - 但以防万一。

异常:

Violation of PRIMARY KEY constraint 'PK_tblRecordFruit_1'. Cannot insert duplicate key in object 'dbo.tblRecordFruit'. The duplicate key value is (112, 53).

表示违反了主键约束。这意味着您正试图复制主键列的值。

查看映射中的以下代码块:

<bag name="Fruits" table="tblRecordFruit" inverse="false" lazy="true" cascade="save-update">
    <key>
        <column name="RecordId" length="4" sql-type="int" not-null="true" />
    </key>

RecordId 似乎是您在 tblRecordFruit table 上的主键。如果是这种情况,很明显你不能将你在问题中提到的示例数据插入 table:

RecordId    FruitTypeId
-----------------------
  112         53
  112         85
  148         16

112 不能在列 RecordId 中重复。如果您的代码试图这样做,很明显您会得到您所说的异常。

您是否正在寻找 composite key(在 RecordIdFruitTypeId 列)?

First, a caveat: composite keys are certainly mappable in NHibernate, but it's a little trickier than a typical single identity key would be. Compared to a normal key, there's some extra setup work, queries are more painful, and they tend to be less optimized in terms of lazy loading. Because of these things, experienced NHibernate users often avoid composite keys entirely when possible.
....
....

<composite-id>
    <key-many-to-one class="SuperShop.Domain.OrderItemComponent,SuperShop.Domain" name="OrderItemComponent" column="OrderItemProductID" />
    <key-property name="DetailType" column="DetailTypeID" type="SuperShop.Domain.DetailTypes,SuperShop.Domain" />
</composite-id>
<version name="LastModifiedOn"....