5

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

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

リポジトリ基本クラスでは、ベスト プラクティスに従って、SaveOrUpdate、Delete、Get メソッドの周りに常にトランザクションをラップしました。

        public virtual void Save(T entity)
        {
          var session = DependencyManager.Resolve<ISession>();
          using (var transaction = session.BeginTransaction())
          {
            session.SaveOrUpdate(entity);
            transaction.Commit();
          }
        }

しかし、保存、削除などの複数のリポジトリ呼び出しを含めるためにアプリケーションサービスなどのどこかにトランザクションを配置する必要がある場合、これは機能しません。

そこで私たちが試みたのは、TransactionScope を使用することです (独自のトランザクション マネージャーを作成したくありませんでした)。これが機能することをテストするために、.Complete() を呼び出さない外側の TransactionScope を使用してロールバックを強制します。

リポジトリ保存():

    public virtual void Save(T entity)
    {
        using (TransactionScope scope = new TransactionScope())
        {
            var session = DependencyManager.Resolve<ISession>();
            session.SaveOrUpdate(entity);
            scope.Complete();
        }   
    }  

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

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

        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

4 に答える 4

2

私は非常によく似たアプローチを取りました。HttpModule では、sessionfactory に新しいセッションを要求し、新しいリクエストが来たときにそれをバインドします。しかし、トランザクションもここで開始します。次に、リクエストが終了したら、バインドを解除してトランザクションをコミットしようとします。

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

これはあなたが取り組んでいるプロジェクトには適切ではないかもしれませんが、私はこのアプローチを好みます。なぜなら、各リクエストは単一のアトミック ユニットとして失敗または成功するからです。実際の実装に興味がある場合は、 ここにソース コードを含む完全なブログ投稿があります。

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

public abstract class NHibernateRepository<T> where T : class
{

    protected readonly ISessionBuilder mSessionBuilder;

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

    public T Retrieve(int id)
    {
            ISession session = GetSession();

            return session.Get<T>(id);
    }

    public void Save(T entity)
    {
            ISession session = GetSession();

            session.SaveOrUpdate(entity);
    }

    public void Delete(T entity)
    {
            ISession session = GetSession();

            session.Delete(entity);
    }

    public IQueryable<T> RetrieveAll() 
    { 
            ISession session = GetSession();

            var query = from Item in session.Linq<T>() select Item; 

            return query; 
    }

    protected virtual ISession GetSession()
    {
        return mSessionBuilder.CurrentSession;
    }
}
于 2009-10-05T18:39:41.387 に答える
1

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

using (TransactionScope tx = new TransactionScope())
{
  using (ISession session1 = ...)
  using (ITransaction tx1 = session.BeginTransaction())
  {
    ...do work with session
    tx1.Commit();
  }

  using (ISession session2 = ...)
  using (ITransaction tx2 = session.BeginTransaction())
  {
    ...do work with session
    tx2.Commit();
  }

  tx.Complete();
}
于 2011-05-06T16:04:18.523 に答える
1

を使用して、トランザクションがアクティブかどうかを実際に確認できますSession.Transaction.IsActive。アクティブでない場合は、作成できます。Transactこのほとんどを自動的に行うメソッドを作成することもできます。主にNHibernate 3.0 Cookbookからの抜粋を次に示します。

// based on NHibernate 3.0 Cookbook, Data Access Layer, pg. 192
public class GenericDataAccessObject<TId> : IGenericDataAccessObject<TId>
{
    // if you don't want to new up your DAO per Unit-of-work you can
    // resolve the session at the time it's accessed.
    private readonly ISession session;

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

    protected ISession Session { get { return session;  } }

    public virtual T Get<T>(TId id)
    {
        return Transact(() => Session.Get<T>(id));
    }

    protected virtual void Save<T>(T entity)
    {
        Transact(() => Session.Save(entity));
    }

    /// <summary>
    /// Perform func within a transaction block, creating a new active transaction
    /// when necessary. No error handling is performed as this function doesn't have
    /// sufficient information to provide a useful error message.
    /// </summary>
    /// <typeparam name="TResult">The return type</typeparam>
    /// <param name="func">The function wrapping the db operations</param>
    /// <returns>The results returned by <c>func</c></returns>
    protected TResult Transact<TResult>(Func<TResult> func)
    {
        // the null Transaction shouldn't happen in a well-behaving Session
        // implementation
        if (Session.Transaction == null || !Session.Transaction.IsActive)
        {
            TResult result;

            // transaction rollback happens during dispose when necessary
            using (var tx = Session.BeginTransaction())
            {
                result = func.Invoke();
                tx.Commit();
            }
            return result;

            // We purposefully don't catch any exceptions as if we were to catch
            // the error at this point we wouldn't have enough information to describe
            // to the user why it happened -- we could only describe what happened.
        }
        return func.Invoke();
    }

    protected void Transact(Action action)
    {
        Transact<bool>(() =>
                           {
                               action.Invoke();
                               return false;
                           }
            );
    }
}
于 2011-10-26T23:29:47.707 に答える
1

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

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

于 2009-10-06T10:50:34.100 に答える