2009-09-09 13 views
5

Câu hỏi của tôi là liên quan đến tình trạng SQL kết nối, tải, vv dựa trên đoạn mã sau:Có bất kỳ cạm bẫy nào khi sử dụng loại trả về <T> của IEnumerable cho dữ liệu SQL không?

public IEnumberable<MyType> GetMyTypeObjects() 
{ 
    string cmdTxt = "select * from MyObjectTable"; 

    using(SqlConnection conn = new SqlConnection(connString)) 
    { 
    using(SqlCommand cmd = new SqlCommand(cmdTxt, conn)) 
    { 
     conn.Open(); 
     using(SqlDataReader reader = cmd.ExecuteReader()) 
     { 
     while(reader.Read()) 
     { 
      yield return Mapper.MapTo<MyType>(reader); 
     } 
     } 
    } 
    } 
    yield break; 
} 

tôi có thể thấy điều này có thể là một vấn đề nếu có nhiều tiến trình đang chạy mã tương tự với thời gian thực hiện dài giữa lặp của đối tượng IEnumerable, bởi vì các kết nối sẽ được mở lâu hơn, vv Tuy nhiên, nó cũng có vẻ hợp lý rằng điều này sẽ làm giảm việc sử dụng CPU trên máy chủ SQL bởi vì nó chỉ trả về dữ liệu khi đối tượng IEnumerable được sử dụng. Nó cũng làm giảm mức sử dụng bộ nhớ trên máy khách bởi vì máy khách chỉ phải nạp một thể hiện của MyType trong khi nó hoạt động thay vì tải tất cả các lần xuất hiện của MyType (bằng cách lặp qua toàn bộ DataReader và trả về một List hoặc một cái gì đó).

  • Có bất kỳ trường hợp bạn có thể nghĩ về nơi bạn sẽ không muốn sử dụng IEnumerable theo cách này, hoặc bất kỳ trường hợp bạn nghĩ rằng nó phù hợp một cách hoàn hảo?

  • Loại tải này thực hiện trên máy chủ SQL?

  • Đây có phải là thứ bạn sẽ sử dụng trong mã của riêng bạn (chặn bất kỳ đề cập nào về NHibernate, Subsonic, v.v ...) không?

  • -

Trả lời

2

Tôi sẽ không sử dụng nó, bởi vì nó ẩn những gì đang xảy ra, và nó có thể có thể rời khỏi kết nối cơ sở dữ liệu mà không cần xử lý thích hợp.

Đối tượng kết nối sẽ bị đóng khi bạn đọc qua bản ghi cuối cùng và nếu bạn ngừng đọc trước khi đối tượng kết nối sẽ không được xử lý. Ví dụ, nếu bạn biết rằng bạn luôn có mười bản ghi trong một kết quả và chỉ có một vòng lặp đọc mười bản ghi đó từ điều tra viên mà không thực hiện cuộc gọi Đọc lần đọc vượt quá mục cuối cùng, kết nối không được đóng đúng cách. Ngoài ra, nếu bạn muốn sử dụng chỉ một phần của kết quả, bạn không có cách nào để đóng kết nối mà không đọc phần còn lại của các bản ghi.

Ngay cả việc xây dựng trong phần mở rộng cho các điều tra viên có thể gây ra điều này ngay cả khi bạn sử dụng chúng một cách chính xác seamlingy:

foreach (MyType item in GetMyTypeObjects().Take(10)) { 
    ... 
} 
+0

Đó là một điểm tốt, cũng không xem xét điều đó. Tôi rất mù quáng theo cách tôi sẽ sử dụng nó! – scottm

+1

@Guffa: Sử dụng các phương thức mở rộng như 'Take' sẽ không thành vấn đề: Phương thức' GetMyTypeObjects' chỉ là cú pháp tạo ra một đối tượng iterator 'IDisposable'. 'Take' sẽ gọi phương thức' Dispose' của trình lặp khi nó được thực hiện, và sau đó sẽ xử lý kết nối, lệnh, đầu đọc, v.v. – LukeH

+0

@Guffa: Và điều tương tự cũng áp dụng cho bất kỳ phần đọc nào khác của resultset: miễn là bạn gọi 'Dispose' khi bạn đã hoàn thành (hoặc tốt hơn là chỉ bọc mọi thứ trong một khối' using') thì kết nối, lệnh, trình đọc vv cũng sẽ được xử lý. – LukeH

2

Tôi khuyên bạn không nên tối ưu hóa trước. Trong nhiều tình huống, các kết nối sẽ được gộp lại.

Tôi cũng không mong đợi bất kỳ sự khác biệt nào về tải trên SQL Server - truy vấn sẽ đã được biên dịch và sẽ chạy.

+0

Tôi không chắc chắn ý bạn là gì bằng cách tối ưu hóa trước (tôi không cố gắng). Tôi đã nghĩ tải sẽ bị ảnh hưởng vì máy chủ sẽ duy trì vị trí con trỏ trên nhiều kết nối hơn cùng một lúc. – scottm

+0

Bằng cách tối ưu hóa trước, tôi có nghĩa là bạn đang lo lắng về các vấn đề hiệu suất trước khi mã của bạn hoạt động. Bạn đang nghĩ về những vấn đề có thể không tồn tại. –

+0

Tôi hiểu. Tôi cũng không muốn tự viết mã vào một góc, phải thiết kế lại các phương thức truy cập dữ liệu của tôi và các phương thức sử dụng chúng trong tương lai. – scottm

6

Đây không phải là mẫu tôi sẽ theo dõi. Tôi sẽ không lo lắng nhiều về tải trên máy chủ như tôi sẽ về ổ khóa. Theo mô hình này tích hợp quá trình truy xuất dữ liệu vào luồng logic nghiệp vụ của bạn, và điều đó có vẻ giống như một công thức toàn diện cho sự cố; bạn không có ý tưởng gì xảy ra ở phía lặp lại, và bạn đang chèn mình vào nó. Truy xuất dữ liệu của bạn trong một lần chụp, sau đó cho phép mã khách hàng liệt kê nó sau khi bạn đã đóng trình đọc.

+0

Khóa trên các đối tượng SQL? Tôi không nghĩ vậy. Bạn nghĩ gì về việc đưa ra một gợi ý nolock (mà chỉ có thể tồn tại trong một số trường hợp)? – scottm

+1

Điều đó sẽ (rõ ràng) loại bỏ vấn đề khóa, nhưng bạn vẫn giữ người đọc mở trong một khoảng thời gian không xác định (có thể là mãi mãi nếu mã tiêu thụ quên bỏ đi điều tra). Nó có vẻ giống như một nguy cơ tương đối cao cho lợi ích thấp. Nếu có nhu cầu đặc biệt cho thực hành này - mức tiêu thụ bộ nhớ tôi cho là có thể hội đủ điều kiện - thì nó không hoàn toàn là ác, nhưng tôi chắc chắn sẽ không làm cho nó trở thành thói quen và tôi sẽ tìm kiếm các giải pháp thay thế. –

+0

Và như một sang một bên, bạn không có nghĩa là đảm bảo "một ví dụ của MyType". Thật vậy, ngay cả khi mã tiêu thụ chỉ giao dịch với một cá thể tại một thời điểm, GC sẽ không làm sạch chúng ngay lập tức. Có, bạn sẽ có chúng rơi ra khỏi phạm vi và đủ điều kiện cho bộ sưu tập sớm hơn, nhưng nó sẽ không phải là một dấu chân bộ nhớ liên tục. –

1

mối quan tâm của tôi sẽ được rằng bạn đang đặt mình hoàn toàn ở lòng thương xót của mã khách hàng.

Bạn đã đề cập rằng mã gọi có thể giữ kết nối mở lâu hơn là cần thiết, nhưng cũng có khả năng nó sẽ không bao giờ cho phép các đối tượng được đóng/xử lý.

Vì vậy, miễn là mã khách hàng sử dụng foreach, using vv hoặc dứt khoát gọi phương thức của điều tra viên Dispose sau đó bạn đang sử dụng tốt, nhưng không có gì để ngăn chặn nó làm một cái gì đó như thế này là:

var e = GetMyTypeObjects().GetEnumerator(); 
e.MoveNext(); // open the connection etc 

// forget about the enumerator and go away and do something else 
// now the reader, command and connection won't be closed/disposed 
// until the GC kicks in and calls their finalisers 
Các vấn đề liên quan