2011-06-21 21 views
17

Trong đoạn mã dưới đây nếu có ngoại lệ được ném trong khi thực hiện các câu lệnh SQL chúng ta nên mong đợi một rollback ngầm trên giao dịch như giao dịch đã không cam kết, nó đi ra khỏi phạm vi và nó được xử lý:Thực tiễn tốt hơn là gọi lại giao dịch một cách rõ ràng hoặc để một ngoại lệ kích hoạt một cuộn ngược tiềm ẩn?

using (DbTransaction tran = conn.BeginTransaction()) 
{ 
    // 
    // Execute SQL statements here... 
    // 
    tran.Commit(); 
} 

là ở trên một thực tế chấp nhận được, hay người ta phải nắm bắt những ngoại lệ và thực hiện một cách rõ ràng một cuộc gọi đến tran.Rollback() như hình dưới đây:

using (DbTransaction tran = conn.BeginTransaction()) 
{ 
    try 
    { 
     // 
     // Execute SQL statements here... 
     // 
     tran.Commit(); 
    } 
    catch 
    { 
     tran.Rollback(); 
     throw; 
    } 
} 
+1

cái nào truyền đạt ý định tốt nhất? –

Trả lời

19

Cựu. Nếu bạn tra cứu các mẫu MSND về các chủ đề tương tự, chẳng hạn như TransactionScope, tất cả chúng đều có lợi cho việc khôi phục ngầm. Có nhiều lý do khác nhau cho điều đó, nhưng tôi sẽ chỉ cung cấp cho bạn một lý do rất đơn giản: khi bạn bắt ngoại lệ, giao dịch có thể đã được khôi phục đã. Nhiều lỗi quay lại giao dịch đang chờ xử lý và sau đó họ trả lại quyền kiểm soát cho khách hàng, trong đó ADO.Net tăng CLR SqlException sau giao dịch đã được khôi phục trên máy chủ (1205 DEADLOCK là ví dụ điển hình của lỗi này) , do đó, cuộc gọi Rollback() rõ ràng là, tốt nhất, không có op và tệ hơn là lỗi. Nhà cung cấp của DbTransaction (ví dụ: SqlTransaction) nên biết cách xử lý trường hợp này, ví dụ: vì có cuộc trò chuyện rõ ràng giữa máy chủ và máy khách thông báo cho thực tế là giao dịch đã được khôi phục và phương thức Dispose() làm điều đúng.

Một lý do thứ hai là giao dịch có thể được lồng vào nhau, nhưng ngữ nghĩa của ROLLBACK được rằng một rollback cuộn lại tất cả giao dịch, vì vậy bạn chỉ cần gọi nó một lần (không giống như Commit() mà cam kết chỉ giao dịch hầu hết bên trong và có được gọi là ghép nối cho mỗi lần bắt đầu). Một lần nữa, Dispose() làm điều đúng.

Cập nhật

Mẫu MSDN cho SqlConnection.BeginTransaction() thực sự ủng hộ hình thức thứ hai và làm một rõ ràng Rollback() trong khối catch. Tôi nghi ngờ các nhà văn kỹ thuật chỉ đơn giản là dự định để hiển thị trong một mẫu duy nhất cả hai Rollback()Commit(), thông báo như thế nào ông cần phải thêm một khối try/catch thứ hai xung quanh Rollback để tránh chính xác một số vấn đề tôi đã đề cập ban đầu.

+1

Trên thực tế, vấn đề là các nhà cung cấp dữ liệu có thể không thực hiện nó theo cách đó: "Vứt bỏ nên quay trở lại giao dịch. Tuy nhiên, hành vi của Vứt bỏ là nhà cung cấp cụ thể, và không nên thay thế gọi Rollback." qua [MSDN] (http://msdn.microsoft.com/en-us/library/bf2cw321.aspx) –

+0

Điều đó nói rằng, tôi thích phương pháp ngầm. –

+0

@AndreLuus: Tốt bắt. Lưu ý rằng mặc dù MSDN bạn liên kết có văn bản này chỉ cho phiên bản 4.5, tức là. tại thời điểm tôi đã viết câu trả lời này chưa được trên MSDN. –

1

Tôi có xu hướng đồng ý với việc khôi phục "Ngụ ý" dựa trên các đường dẫn ngoại lệ. Nhưng, rõ ràng, điều đó phụ thuộc vào nơi bạn đang ở trong ngăn xếp và những gì bạn đang cố gắng hoàn thành (tức là lớp DBTranscation bắt ngoại lệ và thực hiện dọn dẹp, hoặc là thụ động không "cam kết"?).

Dưới đây là một trường hợp xử lý tiềm ẩn làm cho tinh thần (có thể):

static T WithTranaction<T>(this SqlConnection con, Func<T> do) { 
    using (var txn = con.BeginTransaction()) { 
     return do(); 
    } 
} 

Nhưng, nếu API là khác nhau, việc xử lý các cam kết có thể, quá (cấp, điều này:

static T WithTranaction<T>(this SqlConnection con, Func<T> do, 
    Action<SqlTransaction> success = null, Action<SqlTransaction> failure = null) 
{ 
    using (var txn = con.BeginTransaction()) { 
     try { 
      T t = do(); 
      success(txn); // does it matter if the callback commits? 
      return t; 
     } catch (Exception e) { 
      failure(txn); // does it matter if the callback rolls-back or commits? 
      // throw new Exception("Doh!", e); // kills the transaction for certain 
      // return default(T); // if not throwing, we need to do something (bogus) 
     } 
    } 
} 

Tôi không thể nghĩ quá nhiều trường hợp mà việc quay ngược lại là cách tiếp cận đúng, ngoại trừ nơi có chính sách kiểm soát thay đổi nghiêm ngặt được thực thi Nhưng sau đó, tôi hơi chậm chạp về sự hấp thu.

3

Bạn có thể đi theo một trong hai cách, trước đây ngắn gọn hơn, sau này là ý định tiết lộ nhiều hơn.

Thông báo trước với cách tiếp cận đầu tiên sẽ là gọi RollBack về xử lý giao dịch phụ thuộc vào việc thực hiện cụ thể của trình điều khiển. Hy vọng rằng hầu như tất cả các kết nối .NET làm điều đó. SqlTransaction làm:

private void Dispose(bool disposing) 
{ 
    Bid.PoolerTrace("<sc.SqlInteralTransaction.Dispose|RES|CPOOL> %d#, Disposing\n", this.ObjectID); 
    if (disposing && (this._innerConnection != null)) 
    { 
     this._disposing = true; 
     this.Rollback(); 
    } 
} 

MySQL:

protected override void Dispose(bool disposing) 
{ 
    if ((conn != null && conn.State == ConnectionState.Open || conn.SoftClosed) && open) 
    Rollback(); 
    base.Dispose(disposing); 
} 

Một caveat với cách tiếp cận thứ hai là nó không phải là an toàn để gọi RollBack mà không try-catch khác. This is explicitly stated in the documentation.

Trong ngắn hạn là tốt hơn: nó phụ thuộc vào trình điều khiển, nhưng nó thường tốt hơn để đi cho người đầu tiên, vì những lý do được đề cập bởi Remus.

Ngoài ra, xem What happens to an uncommitted transaction when the connection is closed? để biết cách xử lý kết nối xử lý các cam kết và quay lại.

Các vấn đề liên quan