10

Sử dụng đối tượng TransactionScope để thiết lập giao dịch ngầm không cần truyền qua các cuộc gọi hàm là rất tốt! Tuy nhiên, nếu một kết nối được mở trong khi một kết nối khác đã được mở, điều phối viên giao dịch âm thầm chuyển giao giao dịch được phân phối (cần dịch vụ MSDTC để chạy và chiếm nhiều tài nguyên và thời gian hơn).Thực hành được khuyến nghị cho các giao dịch dừng leo thang để phân phối khi sử dụng giao dịch

Vì vậy, đây là tốt:

 using (var ts = new TransactionScope()) 
     { 
      using (var c = DatabaseManager.GetOpenConnection()) 
      { 
       // Do Work 
      } 
      using (var c = DatabaseManager.GetOpenConnection()) 
      { 
       // Do more work in same transaction using different connection 
      } 
      ts.Complete(); 
     } 

Nhưng điều này leo thang giao dịch:

 using (var ts = new TransactionScope()) 
     { 
      using (var c = DatabaseManager.GetOpenConnection()) 
      { 
       // Do Work 
       using (var nestedConnection = DatabaseManager.GetOpenConnection()) 
       { 
        // Do more work in same transaction using different nested connection - escalated transaction to distributed 
       } 
      } 
      ts.Complete(); 
     } 

Có một thực tế khuyến cáo để tránh leo thang giao dịch theo cách này, trong khi vẫn sử dụng kết nối lồng nhau?

Điều tốt nhất tôi có thể đưa ra vào lúc này là có một kết nối ThreadStatic và tái sử dụng mà nếu Transaction.Current được thiết lập, như vậy:

public static class DatabaseManager 
{ 
    private const string _connectionString = "data source=.\\sql2008; initial catalog=test; integrated security=true"; 

    [ThreadStatic] 
    private static SqlConnection _transactionConnection; 

    [ThreadStatic] private static int _connectionNesting; 

    private static SqlConnection GetTransactionConnection() 
    { 
     if (_transactionConnection == null) 
     { 
      Transaction.Current.TransactionCompleted += ((s, e) => 
      { 
       _connectionNesting = 0; 
       if (_transactionConnection != null) 
       { 
        _transactionConnection.Dispose(); 
        _transactionConnection = null; 
       } 
      }); 

      _transactionConnection = new SqlConnection(_connectionString); 
      _transactionConnection.Disposed += ((s, e) => 
      { 
       if (Transaction.Current != null) 
       { 
        _connectionNesting--; 
        if (_connectionNesting > 0) 
        { 
         // Since connection is nested and same as parent, need to keep it open as parent is not expecting it to be closed! 
         _transactionConnection.ConnectionString = _connectionString; 
         _transactionConnection.Open(); 
        } 
        else 
        { 
         // Can forget transaction connection and spin up a new one next time one's asked for inside this transaction 
         _transactionConnection = null; 
        } 
       } 
      }); 
     } 
     return _transactionConnection; 
    } 

    public static SqlConnection GetOpenConnection() 
    { 
     SqlConnection connection; 
     if (Transaction.Current != null) 
     { 
      connection = GetTransactionConnection(); 
      _connectionNesting++; 
     } 
     else 
     { 
      connection = new SqlConnection(_connectionString); 
     } 
     if (connection.State != ConnectionState.Open) 
     { 
      connection.Open(); 
     } 
     return connection; 
    } 
} 

Edit: Vì vậy, nếu câu trả lời là để tái sử dụng cùng một kết nối khi nó được lồng vào bên trong một transactioncope, như mã ở trên, tôi tự hỏi về các tác động của việc xử lý kết nối giữa giao dịch này.

Cho đến khi tôi có thể nhìn thấy (sử dụng Reflector để kiểm tra mã), cài đặt của kết nối (chuỗi kết nối, vv) được đặt lại và kết nối được đóng lại. Vì vậy, (theo lý thuyết), việc thiết lập lại chuỗi kết nối và mở kết nối trên các cuộc gọi tiếp theo sẽ "tái sử dụng" kết nối và ngăn chặn sự leo thang (và thử nghiệm ban đầu của tôi đồng ý với điều này).

Có vẻ như một chút hacky ... và tôi chắc chắn phải có một thực hành tốt nhất ở đâu đó nói rằng người ta không nên tiếp tục sử dụng một đối tượng sau khi nó được xử lý! Tuy nhiên, vì tôi không thể phân lớp SqlConnection được niêm phong, và muốn duy trì các phương thức kết nối thân thiện với giao dịch của tôi, tôi đấu tranh (nhưng sẽ rất vui) để thấy một cách tốt hơn.

Ngoài ra, nhận ra rằng tôi có thể buộc kết nối không lồng nhau bằng cách ném ngoại lệ nếu mã ứng dụng cố gắng để mở kết nối lồng nhau (mà trong nhiều trường hợp là không cần thiết, trong codebase của chúng tôi)

public static class DatabaseManager 
{ 
    private const string _connectionString = "data source=.\\sql2008; initial catalog=test; integrated security=true; enlist=true;Application Name='jimmy'"; 

    [ThreadStatic] 
    private static bool _transactionHooked; 
    [ThreadStatic] 
    private static bool _openConnection; 

    public static SqlConnection GetOpenConnection() 
    { 
     var connection = new SqlConnection(_connectionString); 
     if (Transaction.Current != null) 
     { 
      if (_openConnection) 
      { 
       throw new ApplicationException("Nested connections in transaction not allowed"); 
      } 

      _openConnection = true; 
      connection.Disposed += ((s, e) => _openConnection = false); 

      if (!_transactionHooked) 
      { 
       Transaction.Current.TransactionCompleted += ((s, e) => 
       { 
        _openConnection = false; 
        _transactionHooked = false; 
       }); 
       _transactionHooked = true; 
      } 
     } 
     connection.Open(); 
     return connection; 
    } 
} 

Sẽ vẫn đánh giá cao một ít giải pháp hacky :)

Trả lời

3

Một trong những lý do chính cho việc leo thang giao dịch là khi bạn có nhiều (khác) kết nối liên quan đến giao dịch. Điều này hầu như luôn luôn leo thang đến một giao dịch phân tán. Và nó thực sự là một nỗi đau.

Đây là lý do tại sao chúng tôi đảm bảo rằng tất cả các giao dịch của chúng tôi sử dụng một đối tượng kết nối duy nhất. Có nhiều hướng khác nhau để làm điều đó. Đối với hầu hết các phần, chúng ta sử dụng đối tượng tĩnh thread để lưu trữ một đối tượng kết nối và các lớp của chúng ta làm việc cơ sở dữ liệu bền vững, sử dụng đối tượng kết nối tĩnh luồng (được chia sẻ tất nhiên). Điều này ngăn cản nhiều đối tượng kết nối đang được sử dụng và đã loại bỏ sự leo thang giao dịch. Bạn cũng có thể đạt được điều này bằng cách đơn giản truyền một đối tượng kết nối từ phương thức này sang phương thức khác, nhưng điều này không phải là sạch, IMO.

+0

:) Tôi đã thêm đề xuất ThreadStatic của mình trong khi bạn viết bài này. Nghe có vẻ như một ý tưởng tốt! – Kram

+0

@Mark - Nó đã làm việc rất tốt cho chúng tôi, và nó tránh chúng tôi phải vượt qua các đối tượng kết nối xung quanh. –

+0

@Randy - Cảm ơn - bạn đã có một cách đẹp hơn để phát hiện khi vứt bỏ() đã được gọi chống lại kết nối threadstatic (như bạn có thể thấy từ ví dụ của tôi ở trên, tôi chỉ cần thiết lập lại chuỗi kết nối và mọi thứ _seems_ là ok. Dường như một chút hacky với tôi mặc dù – Kram