1

私はレガシー データベースを使用しており、ある種のローカライズ テーブルを Fluent NHibernate にマップしようとしています。

テーブルは次のようになります。

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

このテーブルは、次のエンティティを通じてマップされます。

public class DataName : EntityBase
{
    パブリック仮想文字列 TableName { get; 設定; }
    パブリック仮想文字列 FieldName { get; 設定; }
    public virtual int Id { get; 設定; }
    public virtual Language 言語 { get; 設定; }
    public virtual string 説明 { get; 設定; }

    ...
}

public class DataNameMap : ClassMap
{
    public DataNameMap()
    {
        Table("zDataName");

        CompositeId().KeyProperty(x => x.TableName)
            .KeyProperty(x => x.フィールド名)
            .KeyProperty(x => x.Id)
            .KeyProperty(x => x.Language, "LangId").CustomType();

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

ローカリゼーションの目的で「DataName」エンティティに複数のエンティティがマップされており、マッピングは次のようになります。

パブリック クラス キャリア
{

    public virtual int CarrId { get; 設定; }
    パブリック仮想文字列 CarrCode { get; 設定; }
    public virtual List DataNameList { get; 設定; }

    ...
}

パブリック クラス CarrierMap : ClassMap
{
    public CarrierMap()
    {
        Table("キャリア");

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

        HasMany(x => x.DataNameList)
            .KeyColumn("ID")
            .Where("TableName = 'キャリア' AND FieldName = '名前'")
            .逆()
            .Cascade.AllDeleteOrphan();

        ...
    }
}

"DataName" テーブルには、次のようなレコードが含まれています。

TableName FieldName 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 であるとします。

UPDATE DataName SET Id = 2000
WHERE TableName = 'Carriers' AND FieldName = 'Name' AND LangId = 1 AND Id IS NULL
UPDATE DataName SET Id = 2000
WHERE TableName = 'Carriers' AND FieldName = 'Name' AND LangId = 2 AND Id IS NULL

DataNameList コレクションの HasMany マッピングの Inverse() オプションを試してみましたが、この特定の状況には何の影響もないようです。

「Carrier」エンティティが挿入された後に追加の UPDATE ステートメントを発行することなく、「Carrier」エンティティと「DataName」のコレクションを永続化するように NHibernate に指示するにはどうすればよいですか?

4

1 に答える 1

1

このシナリオ (レガシー データベースに関連している可能性があります) では、 NHibernateは期待どおりに正しく動作しています。理由を説明してみます。FLUSH 順序付けと 1 つの列の二重使用を観察する必要があります。

まず、セッションのフラッシュの順序。

Hibernate のドキュメントには次のように書かれています。

9.6 フラッシュ ( http://nhibernate.info/doc/nh/en/index.html#manipulatingdata-flushing )

...

SQL ステートメントは、次の順序で発行されます。

  • すべてのエンティティの挿入。対応するオブジェクトが ISession.Save() を使用して保存されたのと同じ順序で
  • すべてのエンティティの更新
  • すべてのコレクションの削除
  • すべてのコレクション要素の削除、更新、および挿入
  • すべてのコレクションの挿入
  • すべてのエンティティの削除。対応するオブジェクトが ISession.Delete() を使用して削除されたのと同じ順序で

次に、2 列のマッピングです。

上記のスニペットは、次Idの 2 つの目的で One column: (テーブル DataName 内) を使用します: I.主キーとして (これは INSERT ステートメントで処理されます)

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

Ⅱ.参照 2 つのエンティティ キャリアとして

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

最後に:何が起こるか

それを考えると、答えは次のとおりです。NHibernateは最初のラウンドですべてのエンティティを INSERT する必要があります。多対 1 のマッピングがある場合は、INSERT の一部として行うこともできます。だからDataNames も挿入さCarrierれます。

ただし、この例では、コレクション ( ) も永続化する必要がありDataNameListます。そのため、 NHibernateはすべての (挿入されたばかりの) コレクション項目を、所有者への参照で提供されるように更新します ( Carrier)

提案

これが、この観察された動作を「期待されるもの」としてマークする理由です。UPDATE を回避するために、リスト マッピングを設定しcascade="none"、永続性を手動で管理できます (たとえば、Session.Save(Carrier) を呼び出し、Carrier をすべての DataName に割り当て、Session.Save(DataName) を呼び出します。

...

編集:拡張提案

I. INSERT と UPDATE を生成する設定

この設定により、DataName への C# クラス キャリア マッピングを経験した動作を繰り返すことができました。

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

XML マッピング (流れるようなものと同じですが、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 (ID) で生成されず、NHibernate (HiLo) によって割り当てられる場合、往復を減らすことができます。

于 2012-11-10T17:29:54.300 に答える