2009-10-05 36 views
5

Tôi đang cố gắng tìm giải pháp tốt nhất để xử lý giao dịch trong một ứng dụng web sử dụng NHibernate.NHibernate, giao dịch và TransactionScope

Chúng tôi sử dụng IHttpModule và tại HttpApplication.BeginRequest chúng tôi mở phiên mới và liên kết nó với HttpContext với ManagedWebSessionContext.Bind (ngữ cảnh, phiên); Chúng tôi đóng và hủy liên kết phiên trên HttpApplication.EndRequest.

Trong lớp cơ sở Repository của chúng tôi, chúng tôi luôn quấn một giao dịch xung quanh SaveOrUpdate của chúng tôi, Xóa, nhận các phương pháp như thế nào, theo best practice:

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

Nhưng sau đó điều này không làm việc, nếu bạn cần phải đặt một giao dịch ở đâu đó trong ví dụ Dịch vụ ứng dụng để bao gồm một số lệnh lưu trữ vào Lưu, Xóa, v.v.

Vì vậy, những gì chúng tôi đã thử là sử dụng TransactionScope (Tôi không muốn viết trình quản lý giao dịch của riêng mình). Để kiểm tra điều này làm việc, tôi sử dụng một TransactionScope ngoài mà không gọi .Complete() để buộc một rollback:

Kho Lưu():

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

Khối sử dụng kho :

 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")); 

Điều này không hiệu quả. Nhưng với tôi nếu NHibernate hỗ trợ TransactionScope nó sẽ! Điều xảy ra là không có ROLLBACK nào trong cơ sở dữ liệu nhưng khi testRepository.GetById (testEntity.Id); tuyên bố được thực hiện một UPDATE với SET Text = "TestCahgned" được kích hoạt thay vào đó (Nó nên đã được bắn giữa BEGIN TRAN và ROLLBACK TRAN). NHibernate đọc giá trị từ bộ nhớ cache level1 và kích hoạt một UPDATE vào cơ sở dữ liệu. Không mong đợi hành vi !? Từ những gì tôi hiểu bất cứ khi nào một rollback được thực hiện trong phạm vi NHibernate bạn cũng cần phải đóng và unbind phiên hiện tại.

Câu hỏi của tôi là: Có ai biết cách tốt để làm điều này bằng cách sử dụng TransactionScope và ManagedWebSessionContext không?

+1

Nếu bạn đang sử dụng TransactionScope, bạn cần sử dụng NHibernate 2.1. Nó chỉ với 2.1 mà NH thực sự tích hợp tốt với TransactionScope. –

Trả lời

2

Tôi đã thực hiện một cách tiếp cận rất giống nhau. Trong HttpModule, tôi yêu cầu sessionfactory cho một phiên mới + liên kết nó khi có một yêu cầu mới. Nhưng tôi cũng bắt đầu giao dịch ở đây. Sau đó, khi yêu cầu kết thúc, tôi chỉ đơn giản là unbind nó và cố gắng cam kết giao dịch.

Cơ sở lưu trữ cơ sở của tôi không tham gia phiên nào theo bất kỳ cách nào - thay vào đó sẽ yêu cầu phiên hiện tại và sau đó thực hiện một số công việc với phiên. Ngoài ra tôi không bọc bất cứ thứ gì bên trong lớp cơ sở này với một giao dịch. Thay vào đó, toàn bộ yêu cầu http là một đơn vị công việc.

Điều này có thể không phù hợp với dự án bạn đang làm, nhưng tôi thích phương pháp này vì mỗi yêu cầu sẽ thất bại hoặc thành công như một đơn vị nguyên tử duy nhất. Tôi có một bài đăng blog đầy đủ here với mã nguồn nếu bạn quan tâm đến việc triển khai thực tế.

dưới đây là một ví dụ về những gì kho cơ sở này trông giống như:

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

Cảm ơn câu trả lời!

Vâng, đó là cách đơn giản và dễ hiểu để giải quyết.Nhưng vấn đề của tôi là tôi muốn đảm bảo rằng có một giao dịch xung quanh một hoạt động kho lưu trữ, ngay cả khi dịch vụ ứng dụng, kho lưu trữ, vv không được gọi bởi yêu cầu web (các loại máy khách khác), do đó tôi muốn có một giao dịch xung quanh mức thấp nhất (ví dụ: session.Save) và sau đó sử dụng TransactionScope để tạo giao dịch dài hơn nếu cần. Nhưng giải pháp của bạn rất đơn giản và tôi thích điều đó, tôi sẽ sử dụng nó và sau đó đảm bảo rằng các khách hàng khác sử dụng giao dịch là tốt.

+0

Vì vậy, bạn đang sử dụng "dịch vụ" này trong bối cảnh WCF/ASMX hay đây có phải là miền giống như dịch vụ bên trong ứng dụng web của bạn không? –

1

Vòng đời giao dịch nên là:

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

Ví dụ hay. Tôi đã sao chép mã của bạn trong câu trả lời của tôi tại "http://stackoverflow.com/a/41255520/5779732". Tôi cũng đã đề cập tên của bạn ở đó và liên kết với câu trả lời này. –

1

Bạn thực sự có thể kiểm tra xem nếu một giao dịch đang hoạt động sử dụng: Session.Transaction.IsActive. Nếu một người không hoạt động, bạn có thể tạo một tài khoản. Bạn cũng có thể tạo phương thức Transact thực hiện hầu hết điều này cho bạn một cách tự động. Dưới đây là một trích đoạn chủ yếu là từ 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; 
          } 
      ); 
    } 
} 
Các vấn đề liên quan