Một SqlException
duy nhất (có thể) bao gồm nhiều lỗi SQL Server. Bạn có thể lặp lại chúng với Errors
bất động sản. Mỗi lỗi là SqlError
:
foreach (SqlError error in exception.Errors)
Mỗi SqlError
có Class
thuộc tính bạn có thể sử dụng để xác định sơ bộ xem bạn có thể thử lại hay không (và trong trường hợp bạn thử lại nếu bạn cũng phải tạo lại kết nối). Từ MSDN:
-
Class
<10 dành cho các lỗi thông tin bạn đã thông qua, sau đó (có thể) bạn không thể thử lại nếu trước tiên bạn không sửa thông tin đầu vào. -
Class
từ 11 đến 16 là "do người dùng tạo" thì có thể một lần nữa bạn không thể làm gì nếu người dùng không sửa đầu vào của mình trước. Xin lưu ý rằng lớp 16 bao gồm nhiều tạm thời lỗi và lớp 13 là dành cho các trường hợp bế tắc (nhờ EvZ), vì vậy bạn có thể loại trừ các lớp này nếu xử lý từng lớp một. -
Class
từ 17 đến 24 là lỗi phần cứng / phần mềm chung và bạn có thể thử lại. KhiClass
là 20 trở lên, bạn phải tạo lại kết nối quá. 22 và 23 có thể là lỗi phần cứng / phần mềm nghiêm trọng, 24 cho biết lỗi phương tiện (người dùng nên cảnh báo điều gì đó nhưng bạn có thể thử lại trong trường hợp đó chỉ là lỗi "tạm thời").
Bạn có thể tìm thấy mô tả chi tiết hơn về từng lớp tại đây.
Nói chung, nếu bạn xử lý lỗi với lớp của chúng, bạn sẽ không cần biết chính xác từng lỗi (sử dụng error.Number
thuộc tính hoặc exception.Number
mà chỉ là một phím tắt cho SqlError
đầu tiên trong danh sách đó). Điều này có hạn chế là bạn có thể thử lại khi nó không hữu ích (hoặc lỗi không thể khôi phục). Tôi đề xuất phương pháp tiếp cận hai bước :
- Kiểm tra các mã lỗi đã biết (liệt kê các mã lỗi với
SELECT * FROM master.sys.messages
) để xem những gì bạn muốn xử lý (biết cách thực hiện). Chế độ xem đó chứa các thư bằng tất cả các ngôn ngữ được hỗ trợ, vì vậy bạn có thể cần lọc chúng theomsglangid
(ví dụ:1033 cho tiếng Anh). - Đối với mọi thứ khác, hãy dựa vào lớp lỗi, hãy thử lại khi
Class
là 13 hoặc cao hơn 16 (và sẽ kết nối lại nếu 20 trở lên). - Các lỗi có mức độ nghiêm trọng cao hơn 21 (22, 23 và 24) là lỗi nghiêm trọng và việc chờ đợi ít sẽ không khắc phục được sự cố đó (bản thân cơ sở dữ liệu cũng có thể bị hỏng).
Một từ về các lớp cao hơn. Cách xử lý những lỗi này không đơn giản và nó phụ thuộc vào nhiều yếu tố (bao gồm cả quản lý rủi ro cho ứng dụng của bạn). Bước đầu tiên đơn giản, tôi sẽ không thử lại cho 22, 23 và 24 khi cố gắng ghi:nếu cơ sở dữ liệu, hệ thống tệp hoặc phương tiện bị hỏng nghiêm trọng thì việc ghi dữ liệu mới có thể làm giảm tính toàn vẹn của dữ liệu hơn nữa (SQL Server cực kỳ cẩn thận để không làm tổn hại đến DB cho một truy vấn ngay cả trong những trường hợp quan trọng). Một máy chủ bị hỏng, nó phụ thuộc vào kiến trúc mạng DB của bạn, thậm chí có thể được hoán đổi nóng (tự động, sau một khoảng thời gian cụ thể hoặc khi một trình kích hoạt được chỉ định được kích hoạt). Luôn tham khảo ý kiến và làm việc chặt chẽ với DBA của bạn.
Chiến lược để thử lại tùy thuộc vào lỗi bạn đang xử lý:tài nguyên trống, đợi một thao tác đang chờ xử lý hoàn tất, thực hiện một hành động thay thế, v.v. Nói chung, bạn chỉ nên thử lại nếu tất cả lỗi là "có thể thử lại":
bool rebuildConnection = true; // First try connection must be open
for (int i=0; i < MaximumNumberOfRetries; ++i) {
try {
// (Re)Create connection to SQL Server
if (rebuildConnection) {
if (connection != null)
connection.Dispose();
// Create connection and open it...
}
// Perform your task
// No exceptions, task has been completed
break;
}
catch (SqlException e) {
if (e.Errors.Cast<SqlError>().All(x => CanRetry(x))) {
// What to do? Handle that here, also checking Number property.
// For Class < 20 you may simply Thread.Sleep(DelayOnError);
rebuildConnection = e.Errors
.Cast<SqlError>()
.Any(x => x.Class >= 20);
continue;
}
throw;
}
}
Kết thúc mọi thứ trong try
/ finally
để loại bỏ kết nối đúng cách. Với CanRetry()
đơn giản-giả-ngây-thơ này chức năng:
private static readonly int[] RetriableClasses = { 13, 16, 17, 18, 19, 20, 21, 22, 24 };
private static bool CanRetry(SqlError error) {
// Use this switch if you want to handle only well-known errors,
// remove it if you want to always retry. A "blacklist" approach may
// also work: return false when you're sure you can't recover from one
// error and rely on Class for anything else.
switch (error.Number) {
// Handle well-known error codes,
}
// Handle unknown errors with severity 21 or less. 22 or more
// indicates a serious error that need to be manually fixed.
// 24 indicates media errors. They're serious errors (that should
// be also notified) but we may retry...
return RetriableClasses.Contains(error.Class); // LINQ...
}
Một số cách khá phức tạp để tìm danh sách các lỗi không nghiêm trọng tại đây.
Thông thường, tôi nhúng tất cả mã này (bản viết sẵn) vào một phương pháp (nơi tôi có thể ẩn tất cả những thứ bẩn thỉu được thực hiện để tạo / hủy / tạo lại kết nối) với chữ ký này:
public static void Try(
Func<SqlConnection> connectionFactory,
Action<SqlCommand> performer);
Để được sử dụng như thế này:
Try(
() => new SqlConnection(connectionString),
cmd => {
cmd.CommandText = "SELECT * FROM master.sys.messages";
using (var reader = cmd.ExecuteReader()) {
// Do stuff
}
});
Xin lưu ý rằng khung xương (thử lại khi có lỗi) cũng có thể được sử dụng khi bạn không làm việc với SQL Server (thực sự nó có thể được sử dụng cho nhiều hoạt động khác như I / O và những thứ liên quan đến mạng, vì vậy tôi khuyên bạn nên viết một hàm chung và sử dụng lại nó một cách rộng rãi).