1

我正在使用旧数据库,并且正在尝试使用 Fluent NHibernate 映射某种本地化表。

该表如下所示:

DataName
[PK] TableName
[PK] FieldName
[PK] Id
[PK] LangId
Description

此表通过此实体映射:

公共类 DataName : EntityBase
{
    公共虚拟字符串表名 { 获取;放; }
    公共虚拟字符串字段名 { 获取;放; }
    公共虚拟 int Id { 获取;放; }
    公共虚拟语言语言{get; 放; }
    公共虚拟字符串描述{get; 放; }

    ...
}

公共类 DataNameMap : ClassMap
{
    公共数据名称映射()
    {
        表(“zDataName”);

        CompositeId().KeyProperty(x => x.TableName)
            .KeyProperty(x => x.FieldName)
            .KeyProperty(x => x.Id)
            .KeyProperty(x => x.Language, "LangId").CustomType();

        Map(x => x.Description).Column("Descr").Length(2000);
    }
}

为了本地化,我有多个实体映射到“DataName”实体,映射如下所示:

公共类运营商
{

    公共虚拟 int CarrId { 获取;放; }
    公共虚拟字符串 CarrCode { get; 放; }
    公共虚拟列表 DataNameList { 获取;放; }

    ...
}

公共类 CarrierMap : ClassMap
{
    公共运营商地图()
    {
        表(“运营商”);

        Id(x => x.CarrId).GeneratedBy.Identity();
        Map(x => x.CarrCode).Not.Nullable().Length(8);

        HasMany(x => x.DataNameList)
            .KeyColumn("Id")
            .Where("TableName = 'carriers' AND FieldName = 'name'")
            。逆()
            .Cascade.AllDeleteOrphan();

        ...
    }
}

“DataName”表包含如下所示的记录:

表名 字段名 Id LangId 描述
--------- --------- -- ------ ------------
运营商名称 1000 1 英文描述
运营商名称 1000 2 法语描述

我可以毫无问题地从数据库中检索这些信息,但是当需要将新实体(插入或更新)保存到数据库时,NHibernate 不会以正确的顺序执行操作。

INSERT INTO DataName (TableName, FieldName, LangId, Id, Description) VALUES ('Carriers', 'Name', 1, NULL, 'English description')
INSERT INTO DataName (TableName, FieldName, LangId, Id, Description) VALUES ('Carriers', 'Name', 2, NULL, 'French description')

插入载体....

就本例而言,假设数据库生成的 id 为 2000。

更新数据名称集 ID = 2000
WHERE TableName = 'Carriers' AND FieldName = 'Name' AND LangId = 1 AND Id IS NULL
更新数据名称集 ID = 2000
WHERE TableName = 'Carriers' AND FieldName = 'Name' AND LangId = 2 AND Id IS NULL

我曾尝试使用 DataNameList 集合的 HasMany 映射的 Inverse() 选项,但它似乎对这种特殊情况没有任何影响。

如何告诉 NHibernate 持久化“Carrier”实体,然后收集“DataName”,而无需在插入“Carrier”实体后发出额外的 UPDATE 语句?

4

1 回答 1

1

在这种情况下(可能与遗留数据库有关)NHibernate正常工作,正如预期的那样。我会尝试解释原因。我们必须观察 FLUSH 排序和一列的双重使用:

首先,会话刷新的顺序。

NHibernate 文档说:

9.6 刷新(http://nhibernate.info/doc/nh/en/index.html#manipulatingdata-flushing

...

SQL 语句按以下顺序发出

  • 所有实体插入,以相同的顺序使用 ISession.Save() 保存相应的对象
  • 所有实体更新
  • 所有集合删除
  • 所有集合元素的删除、更新和插入
  • 所有集合插入
  • 所有实体删除,以相同的顺序使用 ISession.Delete() 删除相应的对象

其次,双列映射。

上面显示的片段使用一列:(Id在表 DataName 中)有两个目的: I.作为主键(这将在 INSERT 语句期间处理)

CompositeId().KeyProperty(x => x.TableName)
  .KeyProperty(x => x.FieldName)
  .KeyProperty(x => x.Id)
  ...

二、作为参考的两个实体载体

HasMany(x => x.DataNameList)
.KeyColumn("Id")
...

最后:会发生什么

鉴于此,答案是:NHibernate必须在第一轮中插入所有实体。如果会有一些多对一的映射,它也可以作为 INSERT 的一部分来完成。所以DataNames 也被插入Carrier

但在我们的示例中,我们还需要持久化 Collection (the DataNameList)。所以现在NHibernate更新所有(刚刚插入的)集合项以提供参考所有者(Carrier

建议

所以这就是为什么我会将这种观察到的行为标记为“预期的行为”。为了避免更新,我们可以将列表映射设置为cascade="none"并手动管理持久性(例如,调用 Session.Save(Carrier) 然后将 Carrier 分配给每个 DataName 并调用 Session.Save(DataName),

...

编辑:扩展建议

一、产生INSERT和UPDATE的设置

使用此设置,我能够重复您所经历的 C# 类载体映射到 DataName 的行为

private IList<DataName> _dataNames;
public virtual IList<DataName> DataNames
{
  get { return _dataNames ?? (_dataNames = new List<DataName>()); }
  set { _dataNames = value; }
}

XML 映射(与您的 fluent 相同,但反转它的false

<bag name="DataNames" inverse="false" cascade="all-delete-orphan"
  where="TableName = 'carriers' AND FieldName = 'name'" >
  <key column="Id" />
  <one-to-many class="DataName" />
</bag>

以及导致相同问题的代码,您在上面已经描述过:

// create 2 DataNames
DataName dn1 = CreateNew(LangId = 1, "English");
DataName dn2 = CreateNew(LangId = 2, "French");

// insert them into collection
carrier.DataNames.Add(dn1);
carrier.DataNames.Add(dn2);

// persist them all
session.Save(carrier);

这导致 INSERTS with UPDATES

二、工作设置,只需使用 INSERT

如果我进行这些更改:1)将反转更改为true inverse="true" 2)根据其持有者(运营商)显式设置 DataName.Id

// carrier ID must be get from the server, because of identity
session.Save(carrier);

// explicity setting of the ID
dn1.Id = carrier.ID;
dn2.Id = carrier.ID;

// inverse set to false 
carrier.DataNames.Add(dn1);
carrier.DataNames.Add(dn2);

// Update via the carrier entity, will trigger INSERT
session.Update(carrier);

然后只应用 INSERTS 语句。这有帮助吗?

注意:如果 ID 不会在 SQL Server(身份)上生成,而是由 NHibernate (HiLo) 分配,则可以减少往返行程。

于 2012-11-10T17:29:54.300 回答