5


0

NHibernateを使用するWebアプリケーションでトランザクションを処理するための最良の解決策を見つけようとしています。

IHttpModuleを使用し、HttpApplication.BeginRequestで新しいセッションを開き、ManagedWebSessionContext.Bind(context、session)を使用してそれをHttpContextにバインドします。 HttpApplication.EndRequestでセッションを閉じてアンバインドします。

私たちのRepository基本クラスでは、私たちは常にSaveOrUpdate、Delete、Getメソッドのようなトランザクションを次のようにラップしました。

パブリック仮想void保存(Tエンティティ){var session = DependencyManager.Resolve(); using(var transaction = session.BeginTransaction()){session.SaveOrUpdate(entity); transaction.Commit(); }}

あなたがどこかにトランザクションを配置する必要がある場合しかし、その後、これは動作しません。 Save、Deleteなどの複数のリポジトリ呼び出しを含むアプリケーションサービス。

そこで私たちが試みたのはTransactionScopeを使うことです(私は自分自身のtransactionmanagerを書きたくありませんでした)。 これがうまくいったことをテストするために、ロールバックを強制するために.Complete()を呼び出さない外側のTransactionScopeを使用します。

リポジトリ保存():

パブリック仮想無効保存(Tエンティティ){(TransactionScopeスコープ= new TransactionScope()){var session = DependencyManager.Resolve(); session.SaveOrUpdate(entity); scope.Complete(); }}

リポジトリを使用するブロック:

TestEntity testEntity = new TestEntity {Text = "Test1"}; ITestRepository testRepository = DependencyManager.Resolve();

testRepository.Save(testEntity);

using(var scope = new TransactionScope()){TestEntity entityToChange = testRepository.GetById(testEntity.Id);

entityToChange.Text = "TestChanged"; testRepository.Save(entityToChange); }

TestEntity entityChanged = testRepository.GetById(testEntity.Id);

Assert.That(entityChanged.Text、Is.EqualTo( "Test1"));

これはうまくいきません。 しかし、NHibernateがTransactionScopeをサポートしているのであれば、私にはそれが必要です。 何が起こるかというと、データベースにはまったくROLLBACKがありませんが、testRepository.GetById(testEntity.Id);ステートメントはSET Text = "TestCahgned"でUPDATEが実行されます(代わりにBEGIN TRANとROLLBACK TRANの間で起動されているはずです)。 NHibernateはlevel1キャッシュから値を読み込み、データベースにUPDATEを発行します。 予期しない動作です。 ロールバックがNHibernateの範囲内で行われるときはいつでも私が理解していることから、あなたは現在のセッションを閉じそしてアンバインドする必要があります。

私の質問は次のとおりです。TransactionScopeとManagedWebSessionContextを使ってこれを実行するための良い方法を誰かが知っていますか?

4 Answer


2


私はとても似たやり方をしました。 HttpModuleでは、新しいセッションが要求されたときにバインドするために、sessionfactoryに新しいセッションを要求します。 しかし、私もここで取引を始めます。 その後、リクエストが終了したら、単にバインドを解除してトランザクションのコミットを試みます。

また、私のベースリポジトリはいかなる方法でもセッションを取りません - それは代わりに現在のセッションを要求してからセッションで何らかの作業を実行します。 また、この基本クラス内にトランザクションで何もラップしません。 代わりに、httpリクエスト全体が単一の作業単位です。

これはあなたが取り組んでいるプロジェクトには適切ではないかもしれませんが、それぞれのリクエストが失敗したり単一のアトミックユニットとして成功したりするので、このアプローチをお勧めします。 実際の実装に興味があるならば、私は完全なブログ記事 ここをソースコードと共に持っています。

以下は、この基本リポジトリがどのように見えるかのサンプルです。

パブリック抽象クラスNHibernateRepositoryここでT:class {

保護された読み取り専用ISessionBuilder mSessionBuilder。

public NHibernateRepository(){mSessionBuilder = SessionBuilderFactory.CurrentBuilder; }

public T Retrieve(int id){ISessionセッション= GetSession();}

session.Get(id)を返します。 }

public void Save(Tエンティティ){ISession session = GetSession();

session.SaveOrUpdate(entity); }

public void Delete(Tエンティティ){ISession session = GetSession();

session.Delete(エンティティ); }

パブリックIQueryable RetrieveAll(){ISessionセッション= GetSession();

var query = session.Linq()のItemからItemを選択します。

クエリを返します。 }

保護された仮想ISession GetSession(){return mSessionBuilder.CurrentSession; }}


1


答えてくれてありがとう!

はい、それはそれを解決するための単純で直接的な方法です。 しかし、私の問題は、アプリケーションサービス、リポジトリなどがWebリクエスト(他の種類のクライアント)によって呼び出されない場合でも、リポジトリ操作を取り巻くトランザクションがあることを確認したいということです。最低レベル(例: session.Save)を使用してから、必要に応じてTransactionScopeを使用してより長いトランザクションを作成します。 しかし、あなたの解決策は単純です、そして私はそれが好きです、私はそれを使うつもりですそしてそれから他のクライアントが同様にトランザクションを使うことを確認します。


1


トランザクションのライフサイクルは次のようになります。

using(TransactionScope tx = new TransactionScope()){using(ISession session1 = ...)using(ITransaction tx1 = session.BeginTransaction()){...セッションtx1.Commit()を使用します。 }

using(ISession session 2 = ...)using(ITransaction tx 2 = session.BeginTransaction()){...セッションt x 2で作業します。 }

tx.Complete(); }


1


実際にトランザクションがアクティブかどうかを確認するには、 `Session.Transaction.IsActive`を使います。 アクティブでない場合は、作成できます。 これの大部分を自動的に行う `Transact`メソッドを作ることもできます。 これは、主にhttps://rads.stackoverflow.com/amzn/click/ com / 184951304X [NHibernate 3.0 Cookbook]からの抜粋です。

// based on NHibernate 3.0 Cookbook, Data Access Layer, pg. 192
public class GenericDataAccessObject:IGenericDataAccessObject {//作業単位ごとにDAOを新しくしたくない場合は、//アクセス時にセッションを解決できます。 プライベートな読み取り専用のISessionセッション。

protected GenericDataAccessObject(ISession session){this.session = session;}; }

保護されたISessionセッション{get {return session;}} }}

公開仮想T Get(TId id){戻りTransact(()=> Session.Get(id)); }

protected virtual void Save(Tエンティティ){Transact(()=> Session.Save(entity)); }

/// ///トランザクションブロック内でfuncを実行し、必要に応じて新しいアクティブトランザクションを///作成する。 この関数は有用なエラーメッセージを提供するのに十分な情報を持っていないため、エラー処理は行われません。 /// ///戻り型/// db操作をラップする関数/// funcによって返される結果/実装if(Session.Transaction == null ||!Session.Transaction.IsActive){TResult結果;

//(var tx = Session.BeginTransaction()){result = func.Invoke(); //トランザクションロールバックは、必要に応じて破棄中に行われます。 tx.Commit();結果を返します。

//エラーが発生した理由をユーザーに説明するのに十分な情報がないため、意図的に例外をキャッチしません func.Invoke();を返します。 }

protected void Transact(アクションアクション){Transact(()=> {action.Invoke(); falseを返す;}); }}