2009-10-05 6 views
5

NHibernate를 사용하는 웹 응용 프로그램에서 트랜잭션을 처리 할 수있는 최상의 솔루션을 찾으려고합니다.NHibernate, transactions 및 TransactionScope

우리는 IHttpModule을 사용하고 HttpApplication.BeginRequest에서 새로운 세션을 열고 ManagedWebSessionContext.Bind (context, session)를 사용하여 HttpContext에 바인딩합니다. HttpApplication.EndRequest에서 세션을 닫고 바인딩 해제합니다.

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

을하지만 당신은 넣어해야하는 경우 다음이 작동하지 않습니다 : 우리의 저장소 기본 클래스에서

, 우리는 항상 best practice에 따라, 우리의 될 saveOrUpdate 주위에 거래, 삭제 등의 방법을 얻기 포장 예를 들어 어딘가에서 거래 저장, 삭제 등을위한 여러 저장소 호출을 포함하는 응용 프로그램 서비스.

그래서 우리는 TransactionScope (내 자신의 transactionmanager를 쓰고 싶지 않았습니다)를 사용하려고했습니다.

저장소 저장() :이 작동하는지 테스트하려면, 내가 .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를 사용하여이를 수행하는 좋은 방법을 알고 있습니까?

+1

TransactionScope를 사용하는 경우 NHibernate 2.1을 사용해야합니다. NH가 실제로 TransactionScope와 잘 통합 된 것은 2.1입니다. –

답변

2

나는 비슷한 접근 방식을 취했다. HttpModule에서 새 세션에 대한 sessionfactory를 요청하고 + 새 요청이 들어올 때 바인드합니다. 그러나 여기에도 트랜잭션을 시작합니다. 그런 다음 요청이 끝나면 단순히 바인딩을 해제하고 트랜잭션을 커밋하려고 시도합니다.

또한 내 기본 저장소는 어떤 방식 으로든 세션을 가져 오지 않습니다. 대신 현재 세션을 요청한 다음 세션과 일부 작업을 수행합니다. 또한 트랜잭션을 사용하여이 기본 클래스 내부의 내용을 래핑하지 않습니다. 대신 전체 http 요청은 단일 작업 단위입니다.

이 방법은 현재 작업중인 프로젝트에는 적합하지 않지만 각 요청이 하나의 원자 단위로 실패하거나 성공하기 때문에이 방법을 사용하는 것이 좋습니다. 실제 구현에 관심이 있다면 전체 블로그 게시물 here을 소스 코드와 함께 가지고 있습니다. 답에 대한

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; 
    } 
} 
1

감사 :

아래는이 기본 저장소와 같은 모습의 샘플입니다!

네, 간단하고 직접적으로 해결할 수 있습니다.하지만 내 문제는 응용 프로그램 서비스, 저장소 등이 웹 요청 (다른 유형의 클라이언트)에 의해 호출되지 않아도 저장소 작업을 둘러싼 트랜잭션이 있는지 확인하려는 것이므로 트랜잭션을 주변에두고 싶었습니다. 가장 낮은 수준 (예 : session.Save)을 사용하고 필요할 경우 TransactionScope를 사용하여 더 긴 트랜잭션을 만듭니다. 하지만 당신의 솔루션은 간단합니다. 나는 이것을 사용하고 다른 클라이언트가 트랜잭션을 사용하는지 확인합니다.

+0

WCF/ASMX 컨텍스트에서이 "서비스"를 사용하고 있습니까? 아니면 웹 응용 프로그램 내부의 서비스와 같은 도메인입니까? –

1

거래 라이프 사이클은 다음과 같아야합니다 Session.Transaction.IsActive을 : 당신은 실제로 트랜잭션이 사용 활성화되어 있는지 확인할 수 있습니다

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(); 
} 
+1

좋은 예. 내 대답에 "http://stackoverflow.com/a/41255520/5779732"에서 코드를 복사했습니다. 나는 또한 거기에 당신의 이름을 언급하고이 대답에 링크. –

1

. 하나가 활성화되어 있지 않으면 하나를 만들 수 있습니다. 이 중 대부분을 자동으로 수행하는 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; 
          } 
      ); 
    } 
}