NHibernateで多対多の関係で使用するアルゴリズム
-
03-07-2019 - |
質問
写真とタグの間には多対多の関係があります。写真には複数のタグを含めることができ、複数の写真で同じタグを共有できます。
ディレクトリ内の写真をスキャンし、NHibernateに追加するループがあります。そのプロセス中に写真にいくつかのタグが追加されます。 2009年に写真が撮影された2009年タグ。
TagクラスはEqualsおよびGetHashCodeを実装し、Nameプロパティを唯一の署名プロパティとして使用します。写真とタグの両方にサロゲートキーがあり、バージョン管理されています。
次のようなコードがいくつかあります:
public void Import() {
...
foreach (var fileName in fileNames) {
var photo = new Photo { FileName = fileName };
AddDefaultTags(_session, photo, fileName);
_session.Save(photo);
}
...
}
private void AddDefaultTags(…) {
...
var tag =_session.CreateCriteria(typeof(Tag))
.Add(Restriction.Eq(“Name”, year.ToString()))
.UniqueResult<Tag>();
if (tag != null) {
photo.AddTag(tag);
} else {
var tag = new Tag { Name = year.ToString()) };
_session.Save(tag);
photo.AddTag(tag);
}
}
私の問題は、タグが存在しない場合です。新年の最初の写真。 AddDefaultTagsメソッドは、タグがデータベースに存在するかどうかを確認してから作成し、NHibernateに追加します。これは、単一の写真を追加する場合はうまく機能しますが、新しい年に同じ作業単位内で複数の写真をインポートする場合、データベースにまだ存在しないため再度追加されるため失敗します。作業単位を完了すると、タグテーブルに同じ名前の2つのエントリを追加しようとするため失敗します...
私の質問は、上記の状況でNHibernateがデータベース内に1つのタグのみを作成しようとすることを確認する方法です。新しく追加したタグのリストを自分で管理する必要がありますか、それが機能するようにマッピングを設定できますか?
解決
基準が古いデータを返さないようにする場合は、 _session.Flush()
を実行する必要があります。
または、 _session.FlushMode
をAutoに設定することにより、正しく実行できるはずです。
FlushMode.Autoを使用すると、条件が実行される前にセッションが自動的にフラッシュされます。
編集:そして重要です!示したコードを読むとき、作業単位にトランザクションを使用しているようには見えません。トランザクションで作業単位をラップすることをお勧めします-NH2.0 +を使用している場合、FlushMode.Autoが機能するために必要です!
他のヒント
保存するたびにトランザクションをコミットする必要があるたびにチェックするときに新しいタグをデータベースに保存する場合は、
別のアプローチは、写真を処理する前にタグをコレクションに読み込むことです。 その後、ローカルで検索し、必要に応じて新しいタグを追加すると言ったように。フォルダの処理が完了したら、セッションをコミットできます。
質問を正しく解釈していない可能性があるため、マッピングを投稿する必要があります。
これは、典型的な「存在しないものをロックする」ことです。問題。すでに何度か直面しましたが、まだ簡単な解決策はありません。
これまで私が知っているオプションは次のとおりです。
- 楽観的:名前に一意の制約を設定し、セッションの1つをコミット時にスローします。その後、もう一度試してください。別のエラーが発生したときに無限ループで終了しないことを確認する必要があります。
- 悲観的:新しいタグを追加すると、TSQLを使用してTagテーブル全体がロックされます。
- .NETロック:.NETロックを使用してスレッドを同期します。これは、並列トランザクションが同じプロセスにある場合にのみ機能します。
- 独自のセッションを使用してタグを作成します(以下を参照)
例:
public static Tag CreateTag(string name)
{
try
{
using (ISession session = factors.CreateSession())
{
session.BeginTransaction();
Tag existingTag = session.CreateCriteria(typeof(Tag)) /* .... */
if (existingtag != null) return existingTag;
{
session.Save(new Tag(name));
}
session.Transaction.Commit();
}
}
// catch the unique constraint exception you get
catch (WhatEverException ex)
{
// try again
return CreateTag(name);
}
}
これは単純に見えますが、いくつかの問題があります。常に既存のタグまたは作成されたタグを取得します(すぐにコミットされます)。ただし、取得するタグは別のセッションからのものであるため、メインセッション用に切り離されます。カスケード(おそらくはしたくない)または更新を使用してセッションにアタッチする必要があります。
タグの作成はメイントランザクションに結合されなくなりました。これは目標でしたが、トランザクションをロールバックすると、作成されたすべてのタグがデータベースに残ります。つまり、タグの作成はトランザクションの一部ではなくなりました。