2010-09-12 38 views
5

Trong quy trình phê duyệt, tôi muốn đảm bảo rằng các email nhắc nhở được gửi chính xác một lần.EF eqivalent cho các hàng bị ảnh hưởng của SqlCommand.ExecuteNonQuery

Với SqlCommand.ExecuteNonQuery Tôi có thể đảm bảo điều này bằng cách kiểm tra giá trị trả lại. Giải pháp được khuyến nghị sử dụng EF là gì? Theo tài liệu ObjectContext.SaveChanges không trả lại giá trị tương đương.

SqlCommand dụ: (The TransactionScope được sử dụng để rollback cập nhật DB trong trường hợp SendMail thất bại.)

 

Dim sql = "UPDATE LeaveApprovalRequests SET State = 'Reminded'" & 
      " WHERE ID=3 AND State <>'Reminded'" 
Using scope As New TransactionScope 
    Using cnx As New SqlConnection(My.Settings.connectionString) 
     cnx.Open() 
     Dim cmd As New SqlCommand(sql, cnx) 
     If 1 = cmd.ExecuteNonQuery Then 
      SendMail() 
     End If 
     scope.Complete() 
    End Using 
End Using 
 

Bằng cách cho phép đồng thời lạc quan (sử dụng ConcurrencyMode = cố định trên một tài sản rowversion) và bắt OptimisticConcurrencyException tôi có thể xác định xem đối tượng có thực sự được cập nhật trong cửa hàng hay không. Bây giờ TransactionScope (được sử dụng để khôi phục bản cập nhật DB nếu SendMail không thành công) ném một lỗi bế tắc. Tại sao?

 

Using scope As New TransactionScope 
    Using ctx As New ApprovalEntities 
    Try 
     Dim approval = ctx.LeaveApprovalRequests. 
     Where(Function(r) r.ID = 3 And r.State = "Created" 
     ).FirstOrDefault 
     If approval Is Nothing Then 
     Console.WriteLine("not found") 
     Exit Sub 
     End If 
     Threading.Thread.Sleep(4000) 
     approval.State = "Reminded" 
     ctx.SaveChanges() 
     SendMail() 
    Catch ex As OptimisticConcurrencyException 
     Exit Try 
    End Try 
    End Using 
    scope.Complete() 
End Using 
 

Trả lời

0

Theo kết quả của các cuộc thảo luận với Morteza tôi trả lời câu hỏi của tôi như sau.

SaveChanges trả về số lượng đối tượng mà nó dự định cập nhật, không phải số mà nó đã cập nhật trong cửa hàng. Vì vậy, nó phải được sử dụng cùng với OptimisticConcurrencyException để xác định nếu thay đổi đã thành công. Người ta phải xem xét rằng các tài sản khác hơn là một trong những dự định để thay đổi có thể gây ra một OptimisticConcurrencyException.

Đọc một thực thể và cập nhật nó trong cùng một TransactionScope gây ra bế tắc.

Đối tác của tôi "Chỉ gửi một email nếu tôi có thể thay đổi Nhà nước từ tạo ra để nhắc nhở" Tôi sử dụng các giải pháp sau đây:

chia đơn vị ApprovalRequest trong hai với tỷ lệ 1: Hội 1, lối ra OptimisticConcurrencyException, gửi thư trong TransactionScope với SaveChanges.

 

ApprovalRequests 
    ID (PK) 
    RequestedBy 
    ... 
    RowVersion (ConcurrencyMode=Fixed) 

ApprovalRequestStates 
    ApprovalRequest_ID (PK, FK) 
    State (ConcurrencyMode=Fixed) 
 
 

Using ctx As New ApprovalEntities 
    Dim approval = cxt.ApprovalRequests.Where ... 
    Dim state = ctx.ApprovalRequestStates. 
     Where(Function(r) r.ApprovalRequest_ID = approval.ID And r.State = "Created" 
     ).FirstOrDefault() 
    If state Is Nothing Then Exit Sub 
    state.State = "Reminded" 
    Threading.Thread.Sleep(3000) 
    Using scope As New TransactionScope 
     Try 
      ctx.SaveChanges() 
      SendMail() 
      scope.Complete() 
     Catch ex As OptimisticConcurrencyException 
      Exit Try 
     End Try 
    End Using 
End Using 
 

Hãy coi chừng! Cập nhật thực thể con khi tham chiếu nó qua cha mẹ của nó cũng làm cho bản cập nhật DB của cha mẹ - trong trường hợp này ném một OptimisticConcurrencyException không mong muốn. Vì vậy, tôi đã không sử dụng: ApprovalRequests.ApprovalRequestStates.State = "Reminded"

2

Vâng, thực tế là số hàng chính xác bị ảnh hưởng có thể được suy ra từ cuộc gọi đến ObjectContext.SaveChanges().

Nếu bạn có một cái nhìn tại ObjectContext.SaveChanges documentation bạn sẽ thấy:

public int SaveChanges()

  1. Return Value: Số đối tượng trong một gia tăng, thay đổi, hoặc xóa trạng thái khi SaveChanges được gọi.
  2. "SaveChanges hoạt động trong một giao dịch. SaveChanges sẽ quay trở lại giao dịch đó và ném ngoại lệ nếu bất kỳ đối tượng ObjectStateEntry bẩn nào không thể tiếp tục tồn tại."

(1) và (2) về cơ bản có nghĩa là nếu cuộc gọi của bạn để SaveChanges() đã được hoàn thành và bạn không nhận được bất kỳ ngoại lệ sau đó EF đảm bảo rằng giá trị trả về chính xác phản ánh số lượng đối tượng đã được sửa đổi.

Vì vậy, tất cả các bạn cần làm là:

try { 
    // Try to save changes, which may cause a conflict. 
    int num = context.SaveChanges(); 
    SendMail(); 
} 
catch (OptimisticConcurrencyException) { 
    //Refresh the entity, using ClientWins 
    context.Refresh(RefreshMode.ClientWins, yourEntityObject); 
    //SaveChanges again; 
    context.SaveChanges(); 
} 
Khi Refresh với ClientWins được gọi, nó thực thi một truy vấn để lấy giá trị hiện tại của tổ chức này trong cơ sở dữ liệu, bao gồm các dấu thời gian mới. Do đó, tất cả các giá trị trường gốc đã được cập nhật để phản ánh các giá trị cơ sở dữ liệu mới nhất để chúng ta có thể thử SaveChanges() một lần nữa một cách an toàn.

Cập nhật Với ​​Câu hỏi của bạn:
Nhiệm vụ là: chỉ gửi một email nếu tôi có thể thay đổi trạng thái từ tạo ra để nhắc nhở. Vì vậy, nó không có ý nghĩa để lực lượng thông qua các SaveChanges khi xử lý OptimisticConcurrencyException. Trình xử lý lỗi nên thoát nếu thay đổi trạng thái gây ra ngoại lệ và nếu không thử lại toàn bộ tác vụ (đọc và lưu). Làm thế nào tôi có thể làm điều này nếu concintereny lạc quan được kích hoạt thông qua một cột RowVersion và không phải bởi nhà nước chỉ?

Vâng, mỗi lần một dòng được sửa đổi, các rowversion lĩnh vực được tự động cập nhật, vì vậy trong trường hợp của bạn mà có thể là tốt nhất nếu bạn tắt Concurrency Mode trên rowversion và bật tính năng này, đối với Nhà nước tài sản, do đó mã của bạn sẽ đơn giản như:
try { 
    context.SaveChanges(); 
    SendMail(); 
} 
catch (OptimisticConcurrencyException) { 
    // RowVersion Concurrency Mode = Fixed 
    // State Concurrency Mode = None 
    // If it reaches here it means that the State has been changed; 
    // so you do nothing except than throwing the exception 
    throw; 
} 

Nhưng, nếu bạn muốn thiết Concurrency Mode = cố định chỉ cho các tài sản rowversion (như bạn nói), thì nó có nghĩa là s mà bạn có khả năng có thể nhận được OptimisticConcurrencyException cho sự thay đổi trên sân bất kỳ bao gồm Nhà nước, vì vậy nó sẽ làm việc nhiều hơn một chút thực hiện công việc:
try { 
    ctx.SaveChanges(); 
    SendMail; 
} 
catch (OptimisticConcurrencyException) { 
    // RowVersion Concurrency Mode = Fixed 
    // State Concurrency Mode = None 
    // If it reches here it means that ANY/All field(s) has changed 
    // So we need to see if it was State: 
    ctx.Refresh(RefreshMode.ClientWins, approval); 
    ObjectStateEntry ose = ctx.ObjectStateManager.GetObjectStateEntry(approval); 
    string stateValue = ose.OriginalValues["State"].ToString(); 
    // If the value is still "Created" then something else should have changed, 
    // And caused the exception, so we can proceed with the Save: 
    if (stateValue == "Created") { 
     ctx.SaveChanges(); 
     SendMail; 
    } 
    else { 
     // Nope, it was state, so we throw the exception to the caller: 
     throw; 
    } 

+0

Đối với tôi, tài liệu cho biết SaveChanges trả về số lượng đối tượng dự định cập nhật, không phải số mà nó cập nhật trong cửa hàng. Tôi đã xác minh điều này bằng cách thiết lập một điểm ngắt tại SaveChanges(), thay đổi giá trị trong SQL Server và sau đó tiếp tục. –

+0

Chính xác. Và nếu bạn có thể gọi SaveChanges() * mà không có bất kỳ ngoại lệ nào, thì EF đảm bảo rằng số lượng đối tượng dự định được cập nhật, thực sự được cập nhật trong cửa hàng hoặc bạn sẽ nhận được một ngoại lệ. –

+0

Xin lỗi, tôi không đọc bình luận đầu tiên của bạn đúng cách (mặc dù bạn đã làm sáng tỏ phần quan trọng). Trường hợp thử nghiệm ban đầu của tôi không sử dụng đồng thời lạc quan, nhưng trong các kịch bản thế giới thực của tôi, tôi sẽ kích hoạt nó trong hầu hết các trường hợp. Đó là câu hỏi tại sao TransactionScope ném một lỗi bế tắc. –

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