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 :)
:) 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
@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. –
@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